From 9d344a8fc00c360dac662cfcafd3610b52b83e99 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 15:50:15 +0100 Subject: [PATCH 001/115] test: add Filtering cog_load and test scaffold and mocked setup --- .../bot/exts/filtering/test_filtering_cog.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/bot/exts/filtering/test_filtering_cog.py diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py new file mode 100644 index 0000000000..2faafa00cd --- /dev/null +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -0,0 +1,28 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.filtering.filtering import Filtering + + +class FilteringCogLoadTests(unittest.IsolatedAsyncioTestCase): + """Test startup behavior of the Filtering cog (`cog_load`).""" + + def setUp(self) -> None: + """Set up a Filtering cog with a mocked bot and stubbed startup dependencies.""" + self.bot = MagicMock() + self.bot.wait_until_guild_available = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + self.cog = Filtering(self.bot) + + # Stub internals that are not relevant to this unit test. + self.cog.collect_loaded_types = MagicMock() + self.cog.schedule_offending_messages_deletion = AsyncMock() + self.cog._fetch_or_generate_filtering_webhook = AsyncMock(return_value=MagicMock()) + + # `weekly_auto_infraction_report_task` is a discord task loop; patch its start method. + self.start_patcher = patch.object(self.cog.weekly_auto_infraction_report_task, "start") + self.mock_weekly_task_start = self.start_patcher.start() + self.addCleanup(self.start_patcher.stop) From 81681edcaf57762087b570bbd0ff6d44546b175f Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Wed, 25 Feb 2026 15:50:16 +0100 Subject: [PATCH 002/115] docs: import report template (#7) import the report markdown template from the assignment instructions. --- report.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 report.md diff --git a/report.md b/report.md new file mode 100644 index 0000000000..2645790f30 --- /dev/null +++ b/report.md @@ -0,0 +1,92 @@ +# Report for assignment 4 + +This is a template for your report. You are free to modify it as needed. +It is not required to use markdown for your report either, but the report +has to be delivered in a standard, cross-platform format. + +## Project + +Name: + +URL: + +One or two sentences describing it + +## Onboarding experience + +Did you choose a new project or continue on the previous one? + +If you changed the project, how did your experience differ from before? + +## Effort spent + +For each team member, how much time was spent in + +1. plenary discussions/meetings; + +2. discussions within parts of the group; + +3. reading documentation; + +4. configuration and setup; + +5. analyzing code/output; + +6. writing documentation; + +7. writing code; + +8. running code? + +For setting up tools and libraries (step 4), enumerate all dependencies +you took care of and where you spent your time, if that time exceeds +30 minutes. + +## Overview of issue(s) and work done. + +Title: + +URL: + +Summary in one or two sentences + +Scope (functionality and code affected). + +## Requirements for the new feature or requirements affected by functionality being refactored + +Optional (point 3): trace tests to requirements. + +## Code changes + +### Patch + +(copy your changes or the add git command to show them) + +git diff ... + +Optional (point 4): the patch is clean. + +Optional (point 5): considered for acceptance (passes all automated checks). + +## Test results + +Overall results with link to a copy or excerpt of the logs (before/after +refactoring). + +## UML class diagram and its description + +### Key changes/classes affected + +Optional (point 1): Architectural overview. + +Optional (point 2): relation to design pattern(s). + +## Overall experience + +What are your main take-aways from this project? What did you learn? + +How did you grow as a team, using the Essence standard to evaluate yourself? + +Optional (point 6): How would you put your work in context with best software engineering practice? + +Optional (point 7): Is there something special you want to mention here? From 7a66cd43c4d01583281e6cb7ca0a133a100c7345 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 15:53:19 +0100 Subject: [PATCH 003/115] test: cover cog_load behavior when filter list fetch fails --- tests/bot/exts/filtering/test_filtering_cog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 2faafa00cd..58b111505a 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -26,3 +26,18 @@ def setUp(self) -> None: self.start_patcher = patch.object(self.cog.weekly_auto_infraction_report_task, "start") self.mock_weekly_task_start = self.start_patcher.start() self.addCleanup(self.start_patcher.stop) + + async def test_cog_load_when_filter_list_fetch_fails(self): + """`cog_load` should currently raise if loading filter lists from the API fails.""" + self.bot.api_client.get.side_effect = RuntimeError("Simulated site/API outage during cog_load") + + with self.assertRaises(RuntimeError): + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + + # Startup should stop before later steps. + self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() + self.cog.schedule_offending_messages_deletion.assert_not_awaited() + self.mock_weekly_task_start.assert_not_called() From 26d30a82e8a0ed322fb25dd811bb2f5d609b5dcb Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 20:42:22 +0100 Subject: [PATCH 004/115] feat: Add retry error filter and mod alert for filter load failures #6 --- bot/exts/filtering/filtering.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 210ae3fb05..837b552719 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -64,6 +64,8 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday +FILTER_LOAD_MAX_ATTEMPTS = 3 +INITIAL_BACKOFF_SECONDS = 1 async def _extract_text_file_content(att: discord.Attachment) -> str: @@ -122,6 +124,33 @@ async def cog_load(self) -> None: await self.schedule_offending_messages_deletion() self.weekly_auto_infraction_report_task.start() + @staticmethod + def _retryable_filter_load_error(error: Exception) -> bool: + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status == 429 or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + + async def _alert_mods_filter_load_failure(self, error: Exception, attempts: int) -> None: + """Send an alert to mod-alerts when startup fails after all retry attempts.""" + mod_alerts_channel = self.bot.get_channel(Channels.mod_alerts) + if mod_alerts_channel is None: + log.error("Failed to send filtering startup failure alert: #mod-alerts channel is unavailable.") + return + + error_details = f"{error.__class__.__name__}: {error}" + if isinstance(error, ResponseCodeError): + error_details = f"HTTP {error.status} - {error_details}" + + try: + await mod_alerts_channel.send( + ":warning: Filtering failed to load filter lists during startup " + f"after {attempts} attempt(s). Error: `{error_details}`" + ) + except discord.HTTPException: + log.exception("Failed to send filtering startup failure alert to #mod-alerts.") + def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ Subscribe a filter list to the given events. From 8c3f009e262420e335d6416cf510f051da4c91ca Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 20:46:25 +0100 Subject: [PATCH 005/115] feat: add retry with exponential backoff for filter list loading #6 --- bot/exts/filtering/filtering.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 837b552719..850fbae110 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -1,3 +1,4 @@ +import asyncio import datetime import io import json @@ -110,7 +111,32 @@ async def cog_load(self) -> None: await self.bot.wait_until_guild_available() log.trace("Loading filtering information from the database.") - raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") + for attempt in range(1, FILTER_LOAD_MAX_ATTEMPTS + 1): + try: + raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") + break + except Exception as error: + is_retryable = self._retryable_filter_load_error(error) + is_last_attempt = attempt == FILTER_LOAD_MAX_ATTEMPTS + + if not is_retryable: + raise + + if is_last_attempt: + log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) + await self._alert_mods_filter_load_failure(error, attempt) + raise + + backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + log.warning( + "Failed to load filtering data (attempt %d/%d). Retrying in %d second(s): %s", + attempt, + FILTER_LOAD_MAX_ATTEMPTS, + backoff_seconds, + error + ) + await asyncio.sleep(backoff_seconds) + example_list = None for raw_filter_list in raw_filter_lists: loaded_list = self._load_raw_filter_list(raw_filter_list) From 92537609398f8f7490b29a308043eb375f1de6a3 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Thu, 26 Feb 2026 11:08:59 +0100 Subject: [PATCH 006/115] test: add tests for invalid extensions and cogs (#3) --- bot/bot.py | 13 ++- tests/bot/exts/test_extensions.py | 157 ++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 tests/bot/exts/test_extensions.py diff --git a/bot/bot.py b/bot/bot.py index 35dbd1ba4e..5e01824ba9 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,5 +1,6 @@ import asyncio import contextlib +import contextvars from sys import exception import aiohttp @@ -13,6 +14,10 @@ log = get_logger("bot") +_current_extension: contextvars.ContextVar[str | None] = contextvars.ContextVar( + "current_extension", default=None +) + class StartupError(Exception): """Exception class for startup errors.""" @@ -26,9 +31,15 @@ class Bot(BotBase): """A subclass of `pydis_core.BotBase` that implements bot-specific functions.""" def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + # Track extension load failures and tasks so we can report them after all attempts have completed + self.extension_load_failures: dict[str, BaseException] = {} + self._extension_load_tasks: dict[str, asyncio.Task] = {} + + + + async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" with start_transaction(op="cog-load", name=name): diff --git a/tests/bot/exts/test_extensions.py b/tests/bot/exts/test_extensions.py new file mode 100644 index 0000000000..2546873883 --- /dev/null +++ b/tests/bot/exts/test_extensions.py @@ -0,0 +1,157 @@ +import asyncio +import contextlib +import importlib +import sys +import unittest +import unittest.mock +from pathlib import Path +from tempfile import TemporaryDirectory + +import discord + +from bot.bot import Bot + + +class ExtensionLoadingTests(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.http_session = unittest.mock.MagicMock(name="http_session") + + # Set up a Bot instance with minimal configuration for testing extension loading. + self.bot = Bot( + command_prefix="!", + guild_id=123456789012345678, + allowed_roles=[], + http_session=self.http_session, + intents=discord.Intents.none() + ) + + # Avoid blocking in _load_extensions() + async def _instant() -> None: + return None + self.bot.wait_until_guild_available = _instant + + # Ensure clean state + self.bot.extension_load_failures = {} + self.bot._extension_load_tasks = {} + + # Temporary importable package: tmp_root/testexts/__init__.py + modules + self._temp_dir = TemporaryDirectory() + self.addCleanup(self._temp_dir.cleanup) + self.tmp_root = Path(self._temp_dir.name) + + self.pkg_name = "testexts" + self.pkg_dir = self.tmp_root / self.pkg_name + self.pkg_dir.mkdir(parents=True, exist_ok=True) + (self.pkg_dir / "__init__.py").write_text("", encoding="utf-8") + + sys.path.insert(0, str(self.tmp_root)) + self.addCleanup(self._remove_tmp_from_syspath) + self._purge_modules(self.pkg_name) + + # Ensure scheduled tasks execute during tests + self._create_task_patcher = unittest.mock.patch( + "pydis_core.utils.scheduling.create_task", + side_effect=lambda coro, *a, **k: asyncio.create_task(coro), + ) + self._create_task_patcher.start() + self.addCleanup(self._create_task_patcher.stop) + + def _remove_tmp_from_syspath(self) -> None: + """Remove the temporary directory from sys.path.""" + with contextlib.suppress(ValueError): + sys.path.remove(str(self.tmp_root)) + + def _purge_modules(self, prefix: str) -> None: + """Remove modules from sys.modules that match the given prefix.""" + for name in list(sys.modules.keys()): + if name == prefix or name.startswith(prefix + "."): + del sys.modules[name] + + def _write_ext(self, module_name: str, source: str) -> str: + """Write an extension module with the given source code and + return its full import path.""" + (self.pkg_dir / f"{module_name}.py").write_text(source, encoding="utf-8") + full = f"{self.pkg_name}.{module_name}" + self._purge_modules(full) + return full + + async def _run_loader(self) -> None: + """Run the extension loader on the package containing our test extensions.""" + module = importlib.import_module(self.pkg_name) + + await self.bot._load_extensions(module) + + # Wait for all extension load tasks to complete so that exceptions are recorded in the bot's state + tasks = getattr(self.bot, "_extension_load_tasks", {}) or {} + if tasks: + await asyncio.gather(*tasks.values(), return_exceptions=True) + + def _assert_failure_recorded_for_extension(self, ext: str) -> None: + """Assert that a failure is recorded for the given extension.""" + if ext in self.bot.extension_load_failures: + return + for key in self.bot.extension_load_failures: + if key.startswith(ext): + return + self.fail( + f"Expected a failure recorded for {ext!r}. " + f"Recorded keys: {sorted(self.bot.extension_load_failures.keys())}" + ) + + async def test_setup_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_setup_fail", + """ +async def setup(bot): + raise RuntimeError("setup failed") +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_cog_load_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_cogload_fail", + """ +from discord.ext import commands + +class BadCog(commands.Cog): + async def cog_load(self): + raise RuntimeError("cog_load failed") + +async def setup(bot): + await bot.add_cog(BadCog()) +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_add_cog_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_addcog_fail", + """ +from discord.ext import commands + +class DupCog(commands.Cog): + pass + +async def setup(bot): + await bot.add_cog(DupCog()) + await bot.add_cog(DupCog()) +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_import_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_import_fail", + """ +raise RuntimeError("import failed before setup()") + +async def setup(bot): + pass +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) From 96e301845d45bf57b5ba08ef2718094b2a9190fd Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:09:48 +0100 Subject: [PATCH 007/115] fix: updated untracked unit test --- tests/bot/exts/filtering/test_filtering_cog.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 58b111505a..f708cadea0 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -29,7 +29,7 @@ def setUp(self) -> None: async def test_cog_load_when_filter_list_fetch_fails(self): """`cog_load` should currently raise if loading filter lists from the API fails.""" - self.bot.api_client.get.side_effect = RuntimeError("Simulated site/API outage during cog_load") + self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") with self.assertRaises(RuntimeError): await self.cog.cog_load() @@ -41,3 +41,16 @@ async def test_cog_load_when_filter_list_fetch_fails(self): self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() self.cog.schedule_offending_messages_deletion.assert_not_awaited() self.mock_weekly_task_start.assert_not_called() + + async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): + """`cog_load` should continue startup when the API returns filter lists successfully.""" + self.bot.api_client.get.return_value = [] + + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() + self.cog.collect_loaded_types.assert_called_once_with(None) + self.cog.schedule_offending_messages_deletion.assert_awaited_once() + self.mock_weekly_task_start.assert_called_once() From 0bfb84ec95a4f307e73a71bc9e1374dd472555fd Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Thu, 26 Feb 2026 11:12:53 +0100 Subject: [PATCH 008/115] feat: add centralized exception logging for extensions and cogs (#3) --- bot/bot.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/bot/bot.py b/bot/bot.py index 5e01824ba9..4184c26cc4 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,11 +1,15 @@ import asyncio import contextlib import contextvars +import types from sys import exception import aiohttp from discord.errors import Forbidden +from discord.ext import commands from pydis_core import BotBase +from pydis_core.utils import scheduling +from pydis_core.utils._extensions import walk_extensions from pydis_core.utils.error_handling import handle_forbidden_from_block from sentry_sdk import new_scope, start_transaction @@ -38,7 +42,67 @@ def __init__(self, *args, **kwargs): self._extension_load_tasks: dict[str, asyncio.Task] = {} + async def add_cog(self, cog: commands.Cog) -> None: + """ + Add a cog to the bot with exception handling. + Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, + including the extension name if available. + """ + extension = _current_extension.get() + + try: + await super().add_cog(cog) + log.info(f"Cog successfully loaded: {cog.qualified_name}") + + except BaseException as e: + key = extension or f"(unknown)::{cog.qualified_name}" + self.extension_load_failures[key] = e + + log.exception( + "FAILED during add_cog (extension=%s, cog=%s)", + extension, + cog.qualified_name, + ) + # Propagate error + raise + + async def _load_extensions(self, module: types.ModuleType) -> None: + + log.info("Waiting for guild %d to be available before loading extensions.", self.guild_id) + await self.wait_until_guild_available() + + self.all_extensions = walk_extensions(module) + + async def _load_one(extension: str) -> None: + token = _current_extension.set(extension) + + try: + log.info(f"Loading extension: {extension}") + await self.load_extension(extension) + log.info(f"Loaded extension: {extension}") + + except BaseException as e: + self.extension_load_failures[extension] = e + log.exception("FAILED to load extension: %s", extension) + raise + + finally: + _current_extension.reset(token) + + for extension in self.all_extensions: + task = scheduling.create_task(_load_one(extension)) + self._extension_load_tasks[extension] = task + + # Wait for all load tasks to complete so we can report any failures together + await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) + + if self.extension_load_failures: + log.warning( + "Extension/cog load failures (%d): %s", + len(self.extension_load_failures), + ", ".join(sorted(self.extension_load_failures.keys())), + ) async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" From 0995803ea59c5ff207586c18efdfa765c95fde6d Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 11:14:29 +0100 Subject: [PATCH 009/115] feat: add backoff retry loading (#15) --- bot/exts/utils/reminders.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 1b386ec000..7b78b30ef8 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -1,3 +1,4 @@ +import asyncio import random import textwrap import typing as t @@ -44,6 +45,8 @@ # the 2000-character message limit. MAXIMUM_REMINDER_MENTION_OPT_INS = 80 +# setup constants when loading +MAX_RETRY_ATTEMPTS = 3 Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -223,12 +226,23 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" + delay = 5 # seconds await self.bot.wait_until_guild_available() - response = await self.bot.api_client.get( - "bot/reminders", - params={"active": "true"} - ) - + for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): + try: + # response either throws, or is a list of reminders (possibly empty) + response = await self.bot.api_client.get( + "bot/reminders", + params={"active": "true"} + ) + break + except Exception as e: + log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") + if attempt == MAX_RETRY_ATTEMPTS: + log.error("Max retry attempts reached. Failed to load reminders.") + raise + await asyncio.sleep(delay) + delay *= 2 # exponential backoff now = datetime.now(UTC) for reminder in response: From 2fb991a27277ca07db76153ca97f90fe41fbcd79 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:32:47 +0100 Subject: [PATCH 010/115] fix: added filtering cog retry test and fixed cog_load startup tests #13 --- .../bot/exts/filtering/test_filtering_cog.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index f708cadea0..45dbe23e2f 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -1,6 +1,8 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from pydis_core.site_api import ResponseCodeError + from bot.exts.filtering.filtering import Filtering @@ -30,12 +32,16 @@ def setUp(self) -> None: async def test_cog_load_when_filter_list_fetch_fails(self): """`cog_load` should currently raise if loading filter lists from the API fails.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") + mock_alerts_channel = MagicMock() + mock_alerts_channel.send = AsyncMock() + self.bot.get_channel.return_value = mock_alerts_channel - with self.assertRaises(RuntimeError): + with self.assertRaises(OSError): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() - self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() @@ -49,8 +55,27 @@ async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() - self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.assertEqual(self.bot.api_client.get.await_count, 1) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() self.cog.collect_loaded_types.assert_called_once_with(None) self.cog.schedule_offending_messages_deletion.assert_awaited_once() self.mock_weekly_task_start.assert_called_once() + + def test_retryable_filter_load_error(self): + """`_retryable_filter_load_error` should classify temporary failures as retryable.""" + test_cases = ( + (ResponseCodeError(MagicMock(status=429)), True), + (ResponseCodeError(MagicMock(status=500)), True), + (ResponseCodeError(MagicMock(status=503)), True), + (ResponseCodeError(MagicMock(status=400)), False), + (ResponseCodeError(MagicMock(status=404)), False), + (TimeoutError("timeout"), True), + (OSError("os error"), True), + (AttributeError("attr"), False), + (ValueError("value"), False), + ) + + for error, expected_retryable in test_cases: + with self.subTest(error=error): + self.assertEqual(self.cog._retryable_filter_load_error(error), expected_retryable) From 2fe59dd065a51cb288907e247231f5c7c633698a Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 11:37:22 +0100 Subject: [PATCH 011/115] feat: alert mods through discord (#15) Alerts the moderators through a discord error message if the loading of the Reminders Cog has failed. --- bot/exts/utils/reminders.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 7b78b30ef8..8a37972aab 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -33,6 +33,7 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg from bot.utils.messages import send_denial +from bot.utils.modlog import send_log_message log = get_logger(__name__) @@ -240,11 +241,11 @@ async def cog_load(self) -> None: log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") + await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(delay) delay *= 2 # exponential backoff now = datetime.now(UTC) - for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) if not is_valid: @@ -258,6 +259,25 @@ async def cog_load(self) -> None: else: self.schedule_reminder(reminder) + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: + message = textwrap.dedent( + f""" + An error occurred while loading the Reminders Cog, and it failed to initialize properly. + + Error details: + {error} + """ + ) + + await send_log_message( + self.bot, + title="Error: Failed to initialize the Reminders Cog", + text=message, + ping_everyone=True, + icon_url=Icons.token_removed, + colour=discord.Color.red() + ) + def ensure_valid_reminder(self, reminder: dict) -> tuple[bool, discord.TextChannel]: """Ensure reminder channel can be fetched otherwise delete the reminder.""" channel = self.bot.get_channel(reminder["channel_id"]) From 6216c17eb4a2fc94f596cc706be8b4408fc1ad0c Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:52:10 +0100 Subject: [PATCH 012/115] test: Added filtering cog retry-path tests for success and final-failure alerting #13 --- .../bot/exts/filtering/test_filtering_cog.py | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 45dbe23e2f..02c1ecfa41 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -29,39 +29,54 @@ def setUp(self) -> None: self.mock_weekly_task_start = self.start_patcher.start() self.addCleanup(self.start_patcher.stop) - async def test_cog_load_when_filter_list_fetch_fails(self): - """`cog_load` should currently raise if loading filter lists from the API fails.""" + async def test_cog_load_retries_then_succeeds(self): + """`cog_load` should retry temporary failures and complete startup after a successful fetch.""" + self.bot.api_client.get.side_effect = [ + OSError("temporary outage"), + TimeoutError("temporary timeout"), + [], + ] + self.cog._alert_mods_filter_load_failure = AsyncMock() + + with patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") + self.assertEqual(mock_sleep.await_count, 2) + self.cog._alert_mods_filter_load_failure.assert_not_awaited() + self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() + self.cog.collect_loaded_types.assert_called_once_with(None) + self.cog.schedule_offending_messages_deletion.assert_awaited_once() + self.mock_weekly_task_start.assert_called_once() + + async def test_retries_three_times_fails_and_alerts(self): + """`cog_load` should alert and re-raise when all retry attempts fail.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - mock_alerts_channel = MagicMock() - mock_alerts_channel.send = AsyncMock() - self.bot.get_channel.return_value = mock_alerts_channel + self.cog._alert_mods_filter_load_failure = AsyncMock() - with self.assertRaises(OSError): + with ( + patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(OSError), + ): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") + self.assertEqual(mock_sleep.await_count, 2) + self.cog._alert_mods_filter_load_failure.assert_awaited_once() + + error, attempts = self.cog._alert_mods_filter_load_failure.await_args.args + self.assertIsInstance(error, OSError) + self.assertEqual(attempts, 3) # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() self.cog.schedule_offending_messages_deletion.assert_not_awaited() self.mock_weekly_task_start.assert_not_called() - async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): - """`cog_load` should continue startup when the API returns filter lists successfully.""" - self.bot.api_client.get.return_value = [] - - await self.cog.cog_load() - - self.bot.wait_until_guild_available.assert_awaited_once() - self.assertEqual(self.bot.api_client.get.await_count, 1) - self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") - self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() - self.cog.collect_loaded_types.assert_called_once_with(None) - self.cog.schedule_offending_messages_deletion.assert_awaited_once() - self.mock_weekly_task_start.assert_called_once() - def test_retryable_filter_load_error(self): """`_retryable_filter_load_error` should classify temporary failures as retryable.""" test_cases = ( From 685788e269145a21eb00ab3a9622190e4f074683 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 11:56:56 +0100 Subject: [PATCH 013/115] feat: add helper for checking if error is retryable (#16) --- bot/exts/info/python_news.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index c786a9d192..6478eed3d1 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -15,6 +15,9 @@ from bot.log import get_logger from bot.utils.webhooks import send_webhook +PYTHON_NEWS_LOAD_MAX_ATTEMPTS = 3 +PYTHON_NEWS_INITIAL_BACKOFF_SECONDS = 1 + PEPS_RSS_URL = "https://peps.python.org/peps.rss" RECENT_THREADS_TEMPLATE = "https://mail.python.org/archives/list/{name}@python.org/recent-threads" @@ -44,6 +47,12 @@ def __init__(self, bot: Bot): self.webhook: discord.Webhook | None = None self.seen_items: dict[str, set[str]] = {} + @staticmethod + def _retryable_site_load_error(error: Exception) -> bool: + if isinstance(error, ResponseCodeError): + return error.status == 429 or error.status >= 500 + return isinstance(error, (TimeoutError, OSError)) + async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" with sentry_sdk.start_span(description="Fetch mailing lists from site"): From 7727c307de22ec1900080e9abd02d92134335a74 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:58:20 +0100 Subject: [PATCH 014/115] fix: updated filter_load_max_attempts contant to reuse connect_max_retries from contants.py #13 --- bot/exts/filtering/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 850fbae110..0bb297495f 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -65,7 +65,7 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday -FILTER_LOAD_MAX_ATTEMPTS = 3 +FILTER_LOAD_MAX_ATTEMPTS = constants.URLs.connect_max_retries INITIAL_BACKOFF_SECONDS = 1 From aa9ec7aaf3017dd03a318a4faf9a134250464422 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 12:02:16 +0100 Subject: [PATCH 015/115] fix: added 408: Request Timeout Error, to imply for retryable and it's unit test #13 --- bot/exts/filtering/filtering.py | 2 +- tests/bot/exts/filtering/test_filtering_cog.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 0bb297495f..5937b4f4d8 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -154,7 +154,7 @@ async def cog_load(self) -> None: def _retryable_filter_load_error(error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): - return error.status == 429 or error.status >= 500 + return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 02c1ecfa41..5299f58b81 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -80,6 +80,7 @@ async def test_retries_three_times_fails_and_alerts(self): def test_retryable_filter_load_error(self): """`_retryable_filter_load_error` should classify temporary failures as retryable.""" test_cases = ( + (ResponseCodeError(MagicMock(status=408)), True), (ResponseCodeError(MagicMock(status=429)), True), (ResponseCodeError(MagicMock(status=500)), True), (ResponseCodeError(MagicMock(status=503)), True), From ea30b974ae14f58f92a71ff6efdf156bfe9d3413 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 12:05:14 +0100 Subject: [PATCH 016/115] test: add test skeleton and valid load test (#15) --- tests/bot/exts/utils/test_reminders.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/bot/exts/utils/test_reminders.py diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py new file mode 100644 index 0000000000..f00952722d --- /dev/null +++ b/tests/bot/exts/utils/test_reminders.py @@ -0,0 +1,31 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.utils.reminders import Reminders +from tests.helpers import MockBot + + +class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): + """ Tests startup behaviour of the Reminders cog. """ + + def setUp(self): + self.bot = MockBot() + self.bot.wait_until_guild_available = AsyncMock() + self.cog = Reminders(self.bot) + + self.cog._alert_mods_if_loading_failed = AsyncMock() + self.cog.ensure_valid_reminder = MagicMock(return_value=(False, None)) + self.cog.schedule_reminder = MagicMock() + self.cog._alert_mods_if_loading_failed = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + @patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) + async def test_reminders_cog_loads(self, sleep_mock): + """ Tests if the Reminders cog loads without error if the GET requests works. """ + self.bot.api_client.get.return_value = [] + try: + await self.cog.cog_load() + except Exception as e: + self.fail(f"Reminders cog failed to load with exception: {e}") From bcf9cb90e082d88aaf10c81306a897d92124f243 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 15:53:11 +0100 Subject: [PATCH 017/115] feat: add retry logic to python_news (#16) Changed cog_load() function to retry connecting to api if it fails initially with an exponential delay and limited max attempts. --- bot/exts/info/python_news.py | 57 ++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 6478eed3d1..53873d5d5c 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -1,3 +1,4 @@ +import asyncio import re import typing as t from datetime import UTC, datetime, timedelta @@ -15,8 +16,8 @@ from bot.log import get_logger from bot.utils.webhooks import send_webhook -PYTHON_NEWS_LOAD_MAX_ATTEMPTS = 3 -PYTHON_NEWS_INITIAL_BACKOFF_SECONDS = 1 +MAX_ATTEMPTS = constants.URLs.connect_max_retries +INITIAL_BACKOFF_SECONDS = 1 PEPS_RSS_URL = "https://peps.python.org/peps.rss" @@ -55,19 +56,45 @@ def _retryable_site_load_error(error: Exception) -> bool: async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" - with sentry_sdk.start_span(description="Fetch mailing lists from site"): - response = await self.bot.api_client.get("bot/mailing-lists") - - for mailing_list in response: - self.seen_items[mailing_list["name"]] = set(mailing_list["seen_items"]) - - with sentry_sdk.start_span(description="Update site with new mailing lists"): - for mailing_list in ("pep", *constants.PythonNews.mail_lists): - if mailing_list not in self.seen_items: - await self.bot.api_client.post("bot/mailing-lists", json={"name": mailing_list}) - self.seen_items[mailing_list] = set() - - self.fetch_new_media.start() + for attempt in range(1, MAX_ATTEMPTS + 1): + try: + with sentry_sdk.start_span(description="Fetch mailing lists from site"): + response = await self.bot.api_client.get("bot/mailing-lists") + + # Rebuild state on each successful fetch (avoid partial state across retries) + self.seen_items = {} + for mailing_list in response: + self.seen_items[mailing_list["name"]] = set(mailing_list["seen_items"]) + + with sentry_sdk.start_span(description="Update site with new mailing lists"): + for mailing_list in ("pep", *constants.PythonNews.mail_lists): + if mailing_list not in self.seen_items: + await self.bot.api_client.post("bot/mailing-lists", json={"name": mailing_list}) + self.seen_items[mailing_list] = set() + + self.fetch_new_media.start() + return + + except Exception as error: + if not self._retryable_site_load_error(error): + raise + + if attempt == MAX_ATTEMPTS: + log.exception( + "Failed to load PythonNews mailing lists after %d attempt(s).", + MAX_ATTEMPTS, + ) + raise + + backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + log.warning( + "Failed to load PythonNews mailing lists (attempt %d/%d). Retrying in %d second(s). Error: %s", + attempt, + MAX_ATTEMPTS, + backoff_seconds, + error, + ) + await asyncio.sleep(backoff_seconds) async def cog_unload(self) -> None: """Stop news posting tasks on cog unload.""" From 200a4d897d5f5e906f387d4ee966d3fb34b6aab5 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 15:55:31 +0100 Subject: [PATCH 018/115] test: test retry functionality (#15) --- bot/exts/utils/reminders.py | 8 +++---- tests/bot/exts/utils/test_reminders.py | 30 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 8a37972aab..37eaf58f3d 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -24,6 +24,7 @@ POSITIVE_REPLIES, Roles, STAFF_AND_COMMUNITY_ROLES, + URLs, ) from bot.converters import Duration, UnambiguousUser from bot.errors import LockedResourceError @@ -47,7 +48,8 @@ MAXIMUM_REMINDER_MENTION_OPT_INS = 80 # setup constants when loading -MAX_RETRY_ATTEMPTS = 3 +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries +BACKOFF_INITIAL_DELAY = 5 # seconds Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -227,7 +229,6 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" - delay = 5 # seconds await self.bot.wait_until_guild_available() for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): try: @@ -243,8 +244,7 @@ async def cog_load(self) -> None: log.error("Max retry attempts reached. Failed to load reminders.") await self._alert_mods_if_loading_failed(e) raise - await asyncio.sleep(delay) - delay *= 2 # exponential backoff + await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index f00952722d..8dfa43064a 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -1,9 +1,12 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from bot.constants import URLs from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries + class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): """ Tests startup behaviour of the Reminders cog. """ @@ -21,11 +24,32 @@ def setUp(self): self.bot.api_client = MagicMock() self.bot.api_client.get = AsyncMock() - @patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) - async def test_reminders_cog_loads(self, sleep_mock): + async def test_reminders_cog_loads_correctly(self): """ Tests if the Reminders cog loads without error if the GET requests works. """ self.bot.api_client.get.return_value = [] try: - await self.cog.cog_load() + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock): + await self.cog.cog_load() except Exception as e: self.fail(f"Reminders cog failed to load with exception: {e}") + + async def test_reminders_cog_load_retries_after_initial_exception(self): + """ Tests if the Reminders cog loads after retrying on initial exception. """ + self.bot.api_client.get.side_effect = [Exception("fail 1"), Exception("fail 2"), []] + try: + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + except Exception as e: + self.fail(f"Reminders cog failed to load after retrying with exception: {e}") + self.assertEqual(mock_sleep.await_count, 2) + self.bot.api_client.get.assert_called() + + async def test_reminders_cog_load_fails_after_max_retries(self): + """ Tests if the Reminders cog fails to load after max retries. """ + self.bot.api_client.get.side_effect = Exception("fail") + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing + self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) + self.bot.api_client.get.assert_called() + self.cog._alert_mods_if_loading_failed.assert_called_once() From 43368677a324cb1b7cb0f53ca7fbb3986a6b9e94 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 15:58:52 +0100 Subject: [PATCH 019/115] fix: rewrite test to pass checks (#15) --- tests/bot/exts/utils/test_reminders.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 8dfa43064a..1117efc63b 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -46,9 +46,11 @@ async def test_reminders_cog_load_retries_after_initial_exception(self): async def test_reminders_cog_load_fails_after_max_retries(self): """ Tests if the Reminders cog fails to load after max retries. """ - self.bot.api_client.get.side_effect = Exception("fail") - with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: - await self.cog.cog_load() + self.bot.api_client.get.side_effect = RuntimeError("fail") + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, \ + self.assertRaises(RuntimeError): + await self.cog.cog_load() + # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) self.bot.api_client.get.assert_called() From 9ce2efa3bd86bee244d861e494b9b5d3a8b65954 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 16:03:53 +0100 Subject: [PATCH 020/115] docs: add comments in the cog_load function (#15) --- bot/exts/utils/reminders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 37eaf58f3d..558560b8ad 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -230,6 +230,7 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" await self.bot.wait_until_guild_available() + # retry fetching reminders with exponential backoff for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): try: # response either throws, or is a list of reminders (possibly empty) From bac8217c13bb3dc5cb2bb846269bc4cfad7d10d6 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 18:05:57 +0100 Subject: [PATCH 021/115] fix: check if the error is retriable (#15) --- bot/exts/utils/reminders.py | 11 +++++++++++ tests/bot/exts/utils/test_reminders.py | 9 ++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 558560b8ad..4f3cc31e59 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -240,6 +240,10 @@ async def cog_load(self) -> None: ) break except Exception as e: + if not self._check_error_is_retriable(e): + log.error(f"Failed to load reminders due to non-retryable error: {e}") + await self._alert_mods_if_loading_failed(e) + raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") @@ -279,6 +283,13 @@ async def _alert_mods_if_loading_failed(self, error: Exception) -> None: colour=discord.Color.red() ) + def _check_error_is_retriable(self, error: Exception) -> bool: + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status in (408, 429) or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + def ensure_valid_reminder(self, reminder: dict) -> tuple[bool, discord.TextChannel]: """Ensure reminder channel can be fetched otherwise delete the reminder.""" channel = self.bot.get_channel(reminder["channel_id"]) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 1117efc63b..04c633cb8f 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -1,6 +1,8 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from pydis_core.site_api import ResponseCodeError + from bot.constants import URLs from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot @@ -35,7 +37,7 @@ async def test_reminders_cog_loads_correctly(self): async def test_reminders_cog_load_retries_after_initial_exception(self): """ Tests if the Reminders cog loads after retrying on initial exception. """ - self.bot.api_client.get.side_effect = [Exception("fail 1"), Exception("fail 2"), []] + self.bot.api_client.get.side_effect = [OSError("fail1"), OSError("fail2"), []] try: with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: await self.cog.cog_load() @@ -46,9 +48,10 @@ async def test_reminders_cog_load_retries_after_initial_exception(self): async def test_reminders_cog_load_fails_after_max_retries(self): """ Tests if the Reminders cog fails to load after max retries. """ - self.bot.api_client.get.side_effect = RuntimeError("fail") + self.bot.api_client.get.side_effect = ResponseCodeError(response=MagicMock(status=500), + response_text="Internal Server Error") with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, \ - self.assertRaises(RuntimeError): + self.assertRaises(ResponseCodeError): await self.cog.cog_load() # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing From 4f209a6ccb4fabde60df0a4126bb87f8dc22daeb Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 18:06:39 +0100 Subject: [PATCH 022/115] feat: alert moderators after max attempts (#16) --- bot/exts/info/python_news.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 53873d5d5c..255d875d45 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -51,9 +51,31 @@ def __init__(self, bot: Bot): @staticmethod def _retryable_site_load_error(error: Exception) -> bool: if isinstance(error, ResponseCodeError): - return error.status == 429 or error.status >= 500 + return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) + async def _alert_mods_python_news_load_failure( + self, error: Exception, attempts: int + ) -> None: + """Alert moderators if PythonNews fails to load after all retries.""" + channel = self.bot.get_channel(constants.Channels.mod_alerts) + + if channel is None: + log.error("Could not find mod_alerts channel to send PythonNews failure alert.") + return + + status_info = "" + if isinstance(error, ResponseCodeError): + status_info = f" (status {error.status})" + + await channel.send( + ":warning: **PythonNews failed to load.**\n" + f"Attempts: {attempts}\n" + f"Error: `{error.__class__.__name__}{status_info}`\n\n" + "Python news posting will not be active until the bot is restarted " + "or the extension is reloaded." + ) + async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" for attempt in range(1, MAX_ATTEMPTS + 1): @@ -84,6 +106,7 @@ async def cog_load(self) -> None: "Failed to load PythonNews mailing lists after %d attempt(s).", MAX_ATTEMPTS, ) + await self._alert_mods_python_news_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) From 3d0ad6cc2fc6dbd1db3179c00f949f24dcca182d Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Fri, 27 Feb 2026 14:41:47 +0100 Subject: [PATCH 023/115] feat: add startup failure status message logs (#3 #5) --- bot/bot.py | 121 ++++++++++++++++----------------- bot/utils/startup_reporting.py | 61 +++++++++++++++++ 2 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 bot/utils/startup_reporting.py diff --git a/bot/bot.py b/bot/bot.py index 4184c26cc4..3d235da920 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -15,6 +15,7 @@ from bot import constants, exts from bot.log import get_logger +from bot.utils.startup_reporting import StartupFailureReporter log = get_logger("bot") @@ -22,7 +23,6 @@ "current_extension", default=None ) - class StartupError(Exception): """Exception class for startup errors.""" @@ -40,69 +40,7 @@ def __init__(self, *args, **kwargs): # Track extension load failures and tasks so we can report them after all attempts have completed self.extension_load_failures: dict[str, BaseException] = {} self._extension_load_tasks: dict[str, asyncio.Task] = {} - - - async def add_cog(self, cog: commands.Cog) -> None: - """ - Add a cog to the bot with exception handling. - - Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, - including the extension name if available. - """ - extension = _current_extension.get() - - try: - await super().add_cog(cog) - log.info(f"Cog successfully loaded: {cog.qualified_name}") - - except BaseException as e: - key = extension or f"(unknown)::{cog.qualified_name}" - self.extension_load_failures[key] = e - - log.exception( - "FAILED during add_cog (extension=%s, cog=%s)", - extension, - cog.qualified_name, - ) - # Propagate error - raise - - async def _load_extensions(self, module: types.ModuleType) -> None: - - log.info("Waiting for guild %d to be available before loading extensions.", self.guild_id) - await self.wait_until_guild_available() - - self.all_extensions = walk_extensions(module) - - async def _load_one(extension: str) -> None: - token = _current_extension.set(extension) - - try: - log.info(f"Loading extension: {extension}") - await self.load_extension(extension) - log.info(f"Loaded extension: {extension}") - - except BaseException as e: - self.extension_load_failures[extension] = e - log.exception("FAILED to load extension: %s", extension) - raise - - finally: - _current_extension.reset(token) - - for extension in self.all_extensions: - task = scheduling.create_task(_load_one(extension)) - self._extension_load_tasks[extension] = task - - # Wait for all load tasks to complete so we can report any failures together - await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) - - if self.extension_load_failures: - log.warning( - "Extension/cog load failures (%d): %s", - len(self.extension_load_failures), - ", ".join(sorted(self.extension_load_failures.keys())), - ) + self._startup_failure_reporter = StartupFailureReporter() async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" @@ -152,3 +90,58 @@ async def on_error(self, event: str, *args, **kwargs) -> None: scope.set_extra("kwargs", kwargs) log.exception(f"Unhandled exception in {event}.") + + async def add_cog(self, cog: commands.Cog) -> None: + """ + Add a cog to the bot with exception handling. + + Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, + including the extension name if available. + """ + extension = _current_extension.get() + + try: + await super().add_cog(cog) + log.info(f"Cog successfully loaded: {cog.qualified_name}") + + except BaseException as e: + key = extension or f"(unknown)::{cog.qualified_name}" + self.extension_load_failures[key] = e + + log.exception( + f"Failed during add_cog (extension={extension}, cog={cog.qualified_name})" + ) + # Propagate error + raise + + async def _load_extensions(self, module: types.ModuleType) -> None: + """Load extensions for the bot.""" + await self.wait_until_guild_available() + + self.all_extensions = walk_extensions(module) + + async def _load_one(extension: str) -> None: + token = _current_extension.set(extension) + + try: + await self.load_extension(extension) + log.info(f"Extension successfully loaded: {extension}") + + except BaseException as e: + self.extension_load_failures[extension] = e + log.exception(f"Failed to load extension: {extension}") + raise + + finally: + _current_extension.reset(token) + + for extension in self.all_extensions: + task = scheduling.create_task(_load_one(extension)) + self._extension_load_tasks[extension] = task + + # Wait for all load tasks to complete so we can report any failures together + await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) + + # Send a Discord message to moderators if any extensions failed to load + if self.extension_load_failures : + await self._startup_failure_reporter.notify(self, self.extension_load_failures) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py new file mode 100644 index 0000000000..b3ad78b67f --- /dev/null +++ b/bot/utils/startup_reporting.py @@ -0,0 +1,61 @@ +import textwrap +from collections.abc import Mapping +from dataclasses import dataclass +from typing import TYPE_CHECKING + +import discord + +from bot.constants import Channels, Icons +from bot.log import get_logger + +log = get_logger(__name__) + +if TYPE_CHECKING: + from bot.bot import Bot + +@dataclass(frozen=True) +class StartupFailureReporter: + """Formats and sends one aggregated startup failure alert to moderators.""" + + async def notify(self, bot: Bot, failures: Mapping[str, BaseException], channel_id: int = Channels.mod_log) -> None: + """Notify moderators of startup failures.""" + if not failures: + return + + if bot.get_channel(channel_id) is None: + # Can't send a message if the channel doesn't exist, so log instead + log.warning("Failed to send startup failure report: mod_log channel not found.") + return + + try: + # Local import avoids circular dependency + from bot.utils.modlog import send_log_message + + text = self.render(failures) + + await send_log_message( + bot, + icon_url=Icons.token_removed, + colour=discord.Colour.red(), + title="Startup: Some extensions failed to load", + text=text, + ping_everyone=True, + channel_id=channel_id + ) + except Exception as e: + log.exception(f"Failed to send startup failure report: {e}") + + def render(self, failures: Mapping[str, BaseException]) -> str: + """Render a human-readable message from the given failures.""" + keys = sorted(failures.keys()) + + lines = [] + lines.append("The following extension(s) failed to load:") + for k in keys: + e = failures[k] + lines.append(f"- **{k}** - `{type(e).__name__}: {e}`") + + return textwrap.dedent(f""" + Failed items: + {chr(10).join(lines)} + """).strip() From d73ee446623845d65c535ec0092764de5f4e058d Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Fri, 27 Feb 2026 15:01:17 +0100 Subject: [PATCH 024/115] docs: add brief project description (#7) --- report.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/report.md b/report.md index 2645790f30..d61048f999 100644 --- a/report.md +++ b/report.md @@ -1,16 +1,13 @@ # Report for assignment 4 -This is a template for your report. You are free to modify it as needed. -It is not required to use markdown for your report either, but the report -has to be delivered in a standard, cross-platform format. - ## Project -Name: +Name: Python Utility Bot -URL: +URL: [https://github.com/python-discord/bot](https://github.com/python-discord/bot) -One or two sentences describing it +A discord bot designed specifically for use with the [Python discord](https://www.pythondiscord.com/) server. +It is built with an extensible cog-based architecture, integrating numerous functionalities, such as moderation, community management, reminders, and many more. ## Onboarding experience From b1d23d42dac9f2a62ac3a7d2dc65a6496073f13b Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Fri, 27 Feb 2026 15:46:55 +0100 Subject: [PATCH 025/115] docs: add onboarding experience (#7) --- report.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index d61048f999..723752ca9e 100644 --- a/report.md +++ b/report.md @@ -11,9 +11,30 @@ It is built with an extensible cog-based architecture, integrating numerous func ## Onboarding experience -Did you choose a new project or continue on the previous one? +### Did you choose a new project or continue on the previous one? -If you changed the project, how did your experience differ from before? +We chose a new project, mainly as it was difficult to find an existing issue which would meet all the requirements set by the assignment. + +### If you changed the project, how did your experience differ from before? + +The project is much more complex than the one we chose for assignment 3, which became evident in the ammount of time needed with setting up the project environnment, and understanding the codebase. + +### Setting up the project + +The project setup was documented extremely well in the project's [Contributing guide](https://www.pythondiscord.com/pages/guides/pydis-guides/contributing/bot/).\ +Installing the dependencies was straightforward using the `uv` package manager. +Most of the time was likely spent downloading and setting up Docker, particularly for those with no prior experience using it. + +In addition to installing dependencies, the project required setting up both the test server and the actual bot and interconnecting them. +This process was also very well documented, and the project even provided a base template for the Discord server, resulting in a very quick setup. + +The project documentation also explained how to run the tests, providing a README file containing all the necessary commands. +It included an introduction to writing new tests, along with a brief overview of how mocking is used, which provided some initial insight. + +However, the documentation did not include an introduction to the actual codebase. +We spent a significant amount of time trying to understand how the project is structured and how different classes interact with one another. +Because the project is tightly integrated with Discord servers, this made it even more challenging, as some functions are triggered exclusively by the Discord API. +We believe that adding concrete examples or a high-level architectural diagram would be highly beneficial for newcomers. ## Effort spent From fe46c4ce0070cbfee4ed7979f94df5d5540c1d73 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Fri, 27 Feb 2026 16:14:29 +0100 Subject: [PATCH 026/115] docs: write issue overview (#7) --- report.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/report.md b/report.md index 723752ca9e..664a5a5572 100644 --- a/report.md +++ b/report.md @@ -62,14 +62,27 @@ you took care of and where you spent your time, if that time exceeds ## Overview of issue(s) and work done. -Title: +Title: Handling of site connection issues during outage. (#2918) -URL: +URL: [https://github.com/python-discord/bot/issues/2918](https://github.com/python-discord/bot/issues/2918) -Summary in one or two sentences +Since some cogs depend on external services (external sites), their initialization fails if those services are unavailable during startup, rendering their functionality inaccessible. +This failure occurs silently, without any indication to moderators. Scope (functionality and code affected). +**Functionality affected** +- Startup behavior of cogs depending on external HTTP services. +- Error handling, error propagation. +- Retry logic with back-off. +- Logging and allerting of moderators. + +**Code affected** +- `cog_load()` implementations in affected cogs. +- Sentry reporting during individual retries and final error. +- Discord message API interaction to alert moderators. +- Associated unit tests covering cog initialization. +- Extension loading failure handling in `bot.py` ## Requirements for the new feature or requirements affected by functionality being refactored Optional (point 3): trace tests to requirements. From b3c9edbc3c10f06a6aa7409a15faa4f8e9974d2c Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Fri, 27 Feb 2026 16:24:51 +0100 Subject: [PATCH 027/115] docs: add requirements (#7) --- report.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/report.md b/report.md index 664a5a5572..73a6f67898 100644 --- a/report.md +++ b/report.md @@ -84,6 +84,16 @@ Scope (functionality and code affected). - Associated unit tests covering cog initialization. - Extension loading failure handling in `bot.py` ## Requirements for the new feature or requirements affected by functionality being refactored +- Update `cog_load()` function for cogs that rely on external sites to handle HTTP errors and exceptions +- The following cogs were identified as pertaining to the problem: + - `bot/ext/filtering/filtering.py` + - `bot/ext/utils/reminders.py` + - `bot/ext/info/python_news.py` + - `bot/ext/moderation/infraction/superstarify.py` +- Add retry logic to repeat the loading for a specified number of times with exponential backoff +- Add logs and alerts of the errors: + - Sentry logging + - Sending a message to `mod-log` Discord channel to alert moderators Optional (point 3): trace tests to requirements. From 9fe59db3b12f66a1f7577ac998ea26aecbd35e0c Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Fri, 27 Feb 2026 16:50:59 +0100 Subject: [PATCH 028/115] docs: rewrite requirements (#7) Rewrote requirements to adhere to the assignment specifications. --- report.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/report.md b/report.md index 73a6f67898..4576972efc 100644 --- a/report.md +++ b/report.md @@ -84,16 +84,24 @@ Scope (functionality and code affected). - Associated unit tests covering cog initialization. - Extension loading failure handling in `bot.py` ## Requirements for the new feature or requirements affected by functionality being refactored -- Update `cog_load()` function for cogs that rely on external sites to handle HTTP errors and exceptions -- The following cogs were identified as pertaining to the problem: - - `bot/ext/filtering/filtering.py` - - `bot/ext/utils/reminders.py` - - `bot/ext/info/python_news.py` - - `bot/ext/moderation/infraction/superstarify.py` -- Add retry logic to repeat the loading for a specified number of times with exponential backoff -- Add logs and alerts of the errors: - - Sentry logging - - Sending a message to `mod-log` Discord channel to alert moderators +### FR-1) Resilient Cog Initialization +Cogs that depend on external HTTP services shall handle connection errors and HTTP failures during `cog_load()` without failing silently. +If the external service is unavailable, the cog must not terminate initialization without reporting the failure. +Identified cogs pertaining to this problem are: +- `bot/ext/filtering/filtering.py` +- `bot/ext/utils/reminders.py` +- `bot/ext/info/python_news.py` +- `bot/ext/moderation/infraction/superstarify.py` + +### FR-2) Retry Mechanism for External HTTP calls +If a cog fails to initialize due to a retriable HTTP error or network-related exception, the system shall automatically retry the initialization a finite number of times before giving up. +The retry attempts shall use exponential backoff to avoid rapid repeated failures. + +### FR-3) Error logging and monitoring +All initialization failures shall be logged through the existing logging infrastructure and reported to Sentry. + +### FR-4) Moderator alert upon failure +If a cog fails to initialize after exhausting all retry attempts, the system shall alert the moderators of the server by sending a message to the `mod-log` Discorrd channel indicating the affected cog and failure description. Optional (point 3): trace tests to requirements. From c989eae4d2165c9329379e6ce700a6015b197b8c Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 16:52:14 +0100 Subject: [PATCH 029/115] test: add superstarify tests (#14) Add test cases for retrying cog loads and skeleton for new functions --- .../moderation/infraction/superstarify.py | 11 ++- .../infraction/test_superstarify_cog.py | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/bot/exts/moderation/infraction/test_superstarify_cog.py diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 006334755d..57b5733a39 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -10,6 +10,7 @@ from bot import constants from bot.bot import Bot +from bot.constants import URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -18,6 +19,7 @@ from bot.utils import time from bot.utils.messages import format_user +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -238,7 +240,14 @@ async def cog_check(self, ctx: Context) -> bool: """Only allow moderators to invoke the commands in this cog.""" return await has_any_role(*constants.MODERATION_ROLES).predicate(ctx) - + async def _fetch_with_retries(self, + retries: int = MAX_RETRY_ATTEMPTS, + params: dict[str, str] | None = None) -> list[dict]: + return None + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: + pass + async def _check_error_is_retriable(self, error: Exception) -> bool: + return False async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py new file mode 100644 index 0000000000..847ce43ba4 --- /dev/null +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -0,0 +1,82 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.moderation.infraction.superstarify import Superstarify +from tests.helpers import MockBot + + +class TestSuperstarify(unittest.IsolatedAsyncioTestCase): + + async def asyncSetUp(self): + self.bot = MockBot() + + self.cog = Superstarify(self.bot) + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + self.cog._alert_mods_if_loading_failed = AsyncMock() + self.cog._check_error_is_retriable = MagicMock(return_value=True) + + async def test_fetch_from_api_success(self): + """API succeeds on first attempt.""" + expected = [{"id": 1}] + self.bot.api_client.get.return_value = expected + + result = await self.cog._fetch_with_retries( + params={"user__id": "123"} + ) + self.assertEqual(result, expected) + + self.bot.api_client.get.assert_awaited_once_with( + "bot/infractions", + params={"user__id": "123"}, + ) + self.cog._alert_mods_if_loading_failed.assert_not_called() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_fetch_retries_then_succeeds(self, _): + self.bot.api_client.get.side_effect = [ + OSError("temporary failure"), + [{"id": 42}], + ] + + result = await self.cog._fetch_with_retries( + params={"user__id": "123"} + ) + + self.assertEqual(result, [{"id": 42}]) + self.assertEqual(self.bot.api_client.get.await_count, 2) + + self.cog._alert_mods_if_loading_failed.assert_not_called() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_fetch_fails_after_max_retries(self, _): + error = OSError("API down") + + self.bot.api_client.get.side_effect = error + + with self.assertRaises(OSError): + await self.cog._fetch_with_retries( + retries=3, + params={"user__id": "123"}, + ) + + self.assertEqual(self.bot.api_client.get.await_count, 3) + + self.cog._alert_mods_if_loading_failed.assert_awaited_once_with(error) + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_non_retriable_error_stops_immediately(self, _): + error = ValueError("bad request") + + self.bot.api_client.get.side_effect = error + self.cog._check_error_is_retriable.return_value = False + + with self.assertRaises(ValueError): + await self.cog._fetch_with_retries() + + # only one attempt + self.bot.api_client.get.assert_awaited_once() + + self.cog._alert_mods_if_loading_failed.assert_awaited_once() From 81f8d34c84e2d81de2ddcad500897e2c02f283fe Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 17:05:54 +0100 Subject: [PATCH 030/115] feat: add logic to functions (#14) Implement skeleton functions with code for retrying fetch, alerting mods, and checking if retryable --- .../moderation/infraction/superstarify.py | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 57b5733a39..8139eaa37d 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -1,16 +1,19 @@ +import asyncio import json import random import textwrap from pathlib import Path +import discord from discord import Embed, Member from discord.ext.commands import Cog, Context, command, has_any_role from discord.utils import escape_markdown +from pydis_core.site_api import ResponseCodeError from pydis_core.utils.members import get_or_fetch_member from bot import constants from bot.bot import Bot -from bot.constants import URLs +from bot.constants import Icons, URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -18,8 +21,10 @@ from bot.log import get_logger from bot.utils import time from bot.utils.messages import format_user +from bot.utils.modlog import send_log_message MAX_RETRY_ATTEMPTS = URLs.connect_max_retries +BACKOFF_INITIAL_DELAY = 5 # seconds log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -45,9 +50,7 @@ async def on_member_update(self, before: Member, after: Member) -> None: f"{after.display_name}. Checking if the user is in superstar-prison..." ) - active_superstarifies = await self.bot.api_client.get( - "bot/infractions", - params={ + active_superstarifies = await self._fetch_with_retries(params={ "active": "true", "type": "superstar", "user__id": str(before.id) @@ -86,9 +89,7 @@ async def on_member_update(self, before: Member, after: Member) -> None: @Cog.listener() async def on_member_join(self, member: Member) -> None: """Reapply active superstar infractions for returning members.""" - active_superstarifies = await self.bot.api_client.get( - "bot/infractions", - params={ + active_superstarifies = await self._fetch_with_retries(params={ "active": "true", "type": "superstar", "user__id": member.id @@ -243,11 +244,43 @@ async def cog_check(self, ctx: Context) -> bool: async def _fetch_with_retries(self, retries: int = MAX_RETRY_ATTEMPTS, params: dict[str, str] | None = None) -> list[dict]: + """Fetch infractions from the API with retries and exponential backoff.""" + for attempt in range(retries): + try: + return await self.bot.api_client.get("bot/infractions", params=params) + except Exception as e: + if attempt == retries - 1 or not self._check_error_is_retriable(e): + await self._alert_mods_if_loading_failed(e) + raise + await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) return None + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - pass + """Alert moderators that loading the superstarify cog failed after retries.""" + message = textwrap.dedent( + f""" + An error occurred while loading the Superstarify Cog, and it failed to initialize properly. + + Error details: + {error} + """ + ) + + await send_log_message( + self.bot, + title="Error: Failed to initialize the Superstarify Cog", + text=message, + ping_everyone=True, + icon_url=Icons.token_removed, + colour=discord.Color.red() + ) async def _check_error_is_retriable(self, error: Exception) -> bool: - return False + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status in (408, 429) or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) From 3b8d7eb9f64aa8997d5d018a8ad12a2ce91d9c6a Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 17:20:30 +0100 Subject: [PATCH 031/115] test: more unit tests for superstarify (#14) Add unit test for on_member_update and unit test to check _alert_mods_if_loading_failed is being called --- .../infraction/test_superstarify_cog.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py index 847ce43ba4..4e3004ce6b 100644 --- a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -80,3 +80,33 @@ async def test_non_retriable_error_stops_immediately(self, _): self.bot.api_client.get.assert_awaited_once() self.cog._alert_mods_if_loading_failed.assert_awaited_once() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_member_update_recovers_from_api_failure(self, _): + before = MagicMock(display_name="Old", id=123) + after = MagicMock(display_name="New", id=123) + after.edit = AsyncMock() + + self.bot.api_client.get.side_effect = [ + OSError(), + [{"id": 42}], + ] + + self.cog.get_nick = MagicMock(return_value="Taylor Swift") + + with patch( + "bot.exts.moderation.infraction._utils.notify_infraction", + new=AsyncMock(return_value=True), + ): + await self.cog.on_member_update(before, after) + + after.edit.assert_awaited_once() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_alert_triggered_after_total_failure(self, _): + self.bot.api_client.get.side_effect = OSError("down") + + with self.assertRaises(OSError): + await self.cog._fetch_with_retries(retries=3) + + self.cog._alert_mods_if_loading_failed.assert_awaited_once() From 5df0dbc01c93a1fdb76ea6958af290f69315406b Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 16:53:02 +0100 Subject: [PATCH 032/115] docs: added example in setting up the project #7 --- report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index 4576972efc..2cbf746195 100644 --- a/report.md +++ b/report.md @@ -17,7 +17,7 @@ We chose a new project, mainly as it was difficult to find an existing issue whi ### If you changed the project, how did your experience differ from before? -The project is much more complex than the one we chose for assignment 3, which became evident in the ammount of time needed with setting up the project environnment, and understanding the codebase. +The project is much more complex than the one we chose for assignment 3, which became evident in the amount of time needed with setting up the project environnment, and understanding the codebase. ### Setting up the project @@ -33,7 +33,7 @@ It included an introduction to writing new tests, along with a brief overview of However, the documentation did not include an introduction to the actual codebase. We spent a significant amount of time trying to understand how the project is structured and how different classes interact with one another. -Because the project is tightly integrated with Discord servers, this made it even more challenging, as some functions are triggered exclusively by the Discord API. +Because the project is tightly integrated with Discord servers, this made it even more challenging, as some functions are triggered exclusively by the Discord API. For example: It was not possible for us to pass `Status Embed` workflow during continuous integration, as the discord channel id was hard-coded directly in the workflow. We believe that adding concrete examples or a high-level architectural diagram would be highly beneficial for newcomers. ## Effort spent From 442d7d82ffa9e13e815f9b092568923443ad2ce9 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 17:12:03 +0100 Subject: [PATCH 033/115] docs: Created table for the Effort spent #7 --- report.md | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/report.md b/report.md index 2cbf746195..52d26f02ef 100644 --- a/report.md +++ b/report.md @@ -38,27 +38,18 @@ We believe that adding concrete examples or a high-level architectural diagram w ## Effort spent -For each team member, how much time was spent in +Estimated effort per team member, in hours: -1. plenary discussions/meetings; +| Team member | Plenary discussions / Group meetings | Reading documentation | Configuration and setup | Analyzing code / output | Writing documentation | Writing code | Running code / tests | Total | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| Apeel | 8 | 3 | 3 | 6 | | 4 | 1 | | +| Josef | | | | | | | | | +| Alexander | | | | | | | | | +| Carl | | | | | | | | | +| Fabian | | | | | | | | | +| Total | | | | | | | | | -2. discussions within parts of the group; -3. reading documentation; - -4. configuration and setup; - -5. analyzing code/output; - -6. writing documentation; - -7. writing code; - -8. running code? - -For setting up tools and libraries (step 4), enumerate all dependencies -you took care of and where you spent your time, if that time exceeds -30 minutes. ## Overview of issue(s) and work done. From 7647464a3676474a4207603180ba8a668c2e22e6 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 17:12:34 +0100 Subject: [PATCH 034/115] docs: created dependencies and setup task table #7 --- report.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/report.md b/report.md index 52d26f02ef..b2532a032b 100644 --- a/report.md +++ b/report.md @@ -49,6 +49,13 @@ Estimated effort per team member, in hours: | Fabian | | | | | | | | | | Total | | | | | | | | | +### Dependencies and setup tasks: + +| Dependency / tool / setup task | Team member(s) | Time spent | Notes | +| --- | --- | --- | --- | +| Docker | All | 1 | Everyone had locally setup the docker to run the project | +| `uv` and Python environment setup | All | 1 | The given documentation had easy guidelines to setup | +| Discord test server / bot configuration | All | 1 | We all have our own test server and individual bots | ## Overview of issue(s) and work done. From 214df2b83b612c8832ce0478bab3a7fa45449337 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 17:52:43 +0100 Subject: [PATCH 035/115] docs: added code changes and patch #7 --- report.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/report.md b/report.md index b2532a032b..3b75fb479f 100644 --- a/report.md +++ b/report.md @@ -107,13 +107,16 @@ Optional (point 3): trace tests to requirements. ### Patch -(copy your changes or the add git command to show them) +The patch can be compared with the `documentation` branch. `documentation` branch only contains report and no changes in the code-base. -git diff ... +```bash +git diff main documentation +``` -Optional (point 4): the patch is clean. +The patch is clean, as it only contains changes related to handling site connection issues during cog initialization, moderator alerting, and the associated tests/documentation. + +The changes were considered for acceptance. All relevant automated checks passed except `Status Embed`. This workflow could not be passed in our local setup because it depends on a hard-coded Discord server channel ID from the official environment, and our test bots do not have permission to send messages to that channel. -Optional (point 5): considered for acceptance (passes all automated checks). ## Test results From 6d0f23fd57130df18df4cc6e74d9cb2f0d23bb45 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 18:16:22 +0100 Subject: [PATCH 036/115] docs: added test results for both after and before implementation #7 --- report.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index 3b75fb479f..f1556ff507 100644 --- a/report.md +++ b/report.md @@ -120,8 +120,17 @@ The changes were considered for acceptance. All relevant automated checks passed ## Test results -Overall results with link to a copy or excerpt of the logs (before/after -refactoring). +Before refactoring, cogs depending on unavailable external services could fail during initialization without notifying moderators. Relevant tests for the new retry/alert behavior were not present. + +After refactoring, the implemented tests passed when run locally with: + +```bash +uv run task test +``` + +### Output Logs: +- Before Implementation: [Before Output Log](https://github.com/rippyboii/python-bot/issues/21#issuecomment-3977441009) +- After Impementation: [After Output Log](https://github.com/rippyboii/python-bot/issues/21#issuecomment-3977445773) ## UML class diagram and its description From 0b89bdb641995663056ba11e3d7cddfaaaf0dcf5 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sat, 28 Feb 2026 18:31:12 +0100 Subject: [PATCH 037/115] fix: remove duplicate moderator notification in filters extension (#3) --- bot/exts/filtering/filtering.py | 1 - .../bot/exts/filtering/test_filtering_cog.py | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 5937b4f4d8..d9cb2c504b 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -124,7 +124,6 @@ async def cog_load(self) -> None: if is_last_attempt: log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) - await self._alert_mods_filter_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 5299f58b81..f9d8536f42 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -51,26 +51,29 @@ async def test_cog_load_retries_then_succeeds(self): self.cog.schedule_offending_messages_deletion.assert_awaited_once() self.mock_weekly_task_start.assert_called_once() - async def test_retries_three_times_fails_and_alerts(self): - """`cog_load` should alert and re-raise when all retry attempts fail.""" - self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - self.cog._alert_mods_filter_load_failure = AsyncMock() + async def test_retries_three_times_fails_and_reraises(self): + """`cog_load` should retry and re-raise when all retry attempts fail.""" + self.bot.api_client.get.side_effect = OSError( + "Simulated site/API outage during cog_load" + ) - with ( - patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, - self.assertRaises(OSError), - ): + with patch( + "bot.exts.filtering.filtering.asyncio.sleep", + new_callable=AsyncMock, + ) as mock_sleep, self.assertRaises(OSError) as ctx: await self.cog.cog_load() + self.assertIs(ctx.exception, self.bot.api_client.get.side_effect) + + # Waited for guild availability self.bot.wait_until_guild_available.assert_awaited_once() + + # 3 attempts self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") - self.assertEqual(mock_sleep.await_count, 2) - self.cog._alert_mods_filter_load_failure.assert_awaited_once() - error, attempts = self.cog._alert_mods_filter_load_failure.await_args.args - self.assertIsInstance(error, OSError) - self.assertEqual(attempts, 3) + # Backoff between attempts (attempts - 1) + self.assertEqual(mock_sleep.await_count, 2) # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() From d582b809ace623edb60e53bbb9be2317832e04bd Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Sat, 28 Feb 2026 18:47:02 +0100 Subject: [PATCH 038/115] docs: updated overall experience section --- report.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/report.md b/report.md index f1556ff507..8755ff53ed 100644 --- a/report.md +++ b/report.md @@ -42,7 +42,7 @@ Estimated effort per team member, in hours: | Team member | Plenary discussions / Group meetings | Reading documentation | Configuration and setup | Analyzing code / output | Writing documentation | Writing code | Running code / tests | Total | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| Apeel | 8 | 3 | 3 | 6 | | 4 | 1 | | +| Apeel | 8 | 3 | 3 | 6 | 3 | 4 | 1 | ~28 | | Josef | | | | | | | | | | Alexander | | | | | | | | | | Carl | | | | | | | | | @@ -140,12 +140,28 @@ Optional (point 1): Architectural overview. Optional (point 2): relation to design pattern(s). + ## Overall experience -What are your main take-aways from this project? What did you learn? +### What are your main take-aways from this project? What did you learn? + +Working on a mature open-source system differs greatly from working on a smaller course project, which is the primary lesson we learned from this project. Even though the setup and contribution guides are well written and provided, understanding the architecture and the interactions between indivdual components and classes takes a significant amount of time. + +Technically, we gained more knowledge about cog-based bot architectures, asynchronous Python systems, and how dependencies on outside services can impact startup behavior. One important lesson was that initialization failures shouldn't go unnoticed. Making the system more reliable and user-friendly requires retrying with exponential backoff, clearly documenting errors, and alerting moderators. + + +### How did you grow as a team, using the Essence standard to evaluate yourself? + + + +### How would you put your work in context with best software engineering practice? + +Our work can be placed in the context of best software engineering practice. The issue we had taken was not only about adding functionality, but about making failure handling more compact in a production-like system that depends on external services. By improving retry behavior, logging, and moderator alerting, we worked toward better observability and fault tolerance, which are important principles in modern software engineering. + +Another important aspect of our team was the communication. Throughout the work, we kept the orignal maintainers informed in the issue channel about our understanding of the problem and the our plan of action. This helped to ensure that our approach matched the project expectations and reduced the risk of introducing unintended behavior or potential vulnerabilities. We also communicated directly with the original developers and maintainers of the bot in their official Discord server, which gave us useful clarification and feedback. -How did you grow as a team, using the Essence standard to evaluate yourself? +Lastly, we provided testing to support the work. We documented known limitations in the test environment, tried to keep the change set restricted to the issue being addressed, and, when feasible, used automated checks to confirm the outcome. -Optional (point 6): How would you put your work in context with best software engineering practice? +### Is there something special you want to mention here? -Optional (point 7): Is there something special you want to mention here? +It's important to note that while the project's setup documentation was excellent, the codebase's architectural overview was lacking. Understanding how the various components, services, and Discord-specific flows work together was more challenging for new contributors than installing or managing the project. We think that future contributors' onboarding experience would be greatly enhanced by including a higher-level architecture overview. From 8ecd6023d0a88c437b9c7b854a04961b25396dfe Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sat, 28 Feb 2026 18:56:20 +0100 Subject: [PATCH 039/115] fix: remove duplicate moderator notification in reminders extension (#3) --- bot/exts/utils/reminders.py | 22 ---------------------- tests/bot/exts/utils/test_reminders.py | 1 - 2 files changed, 23 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 4f3cc31e59..86ab17c76e 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -34,7 +34,6 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg from bot.utils.messages import send_denial -from bot.utils.modlog import send_log_message log = get_logger(__name__) @@ -242,12 +241,10 @@ async def cog_load(self) -> None: except Exception as e: if not self._check_error_is_retriable(e): log.error(f"Failed to load reminders due to non-retryable error: {e}") - await self._alert_mods_if_loading_failed(e) raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") - await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) @@ -264,25 +261,6 @@ async def cog_load(self) -> None: else: self.schedule_reminder(reminder) - async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - message = textwrap.dedent( - f""" - An error occurred while loading the Reminders Cog, and it failed to initialize properly. - - Error details: - {error} - """ - ) - - await send_log_message( - self.bot, - title="Error: Failed to initialize the Reminders Cog", - text=message, - ping_everyone=True, - icon_url=Icons.token_removed, - colour=discord.Color.red() - ) - def _check_error_is_retriable(self, error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 04c633cb8f..67c8904fba 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -57,4 +57,3 @@ async def test_reminders_cog_load_fails_after_max_retries(self): # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) self.bot.api_client.get.assert_called() - self.cog._alert_mods_if_loading_failed.assert_called_once() From 768b2faede41ef8fa8f2cd52c94230ebcf19b5d6 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Sat, 28 Feb 2026 21:22:49 +0100 Subject: [PATCH 040/115] test: mocked setup and pythonNews tests (#16) --- tests/bot/exts/info/test_python_news.py | 132 ++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/bot/exts/info/test_python_news.py diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py new file mode 100644 index 0000000000..0b47c6d7b3 --- /dev/null +++ b/tests/bot/exts/info/test_python_news.py @@ -0,0 +1,132 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from pydis_core.site_api import ResponseCodeError + +from bot.exts.info.python_news import PythonNews + + +class PythonNewsCogLoadTests(unittest.IsolatedAsyncioTestCase): + """Test startup behavior of the PythonNews cog (`cog_load`).""" + + def setUp(self) -> None: + """Set up a PythonNews cog with a mocked bot and stubbed startup dependencies.""" + self.bot = MagicMock() + self.bot.wait_until_guild_available = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + self.bot.api_client.post = AsyncMock() + + # Required by `fetch_new_media` later, but not used in these tests. + self.bot.http_session = MagicMock() + + self.cog = PythonNews(self.bot) + + # Stub out task-loop start, so it doesn't actually schedule anything. + self.start_patcher = patch.object(self.cog.fetch_new_media, "start") + self.mock_fetch_new_media_start = self.start_patcher.start() + self.addCleanup(self.start_patcher.stop) + + async def test_cog_load_retries_then_succeeds(self): + """`cog_load` should retry temporary failures and complete startup after a successful fetch.""" + # First two attempts fail with retryable errors, third succeeds. + self.bot.api_client.get.side_effect = [ + OSError("temporary outage"), + TimeoutError("temporary timeout"), + [ + {"name": "pep", "seen_items": ["1", "2"]}, + ], + ] + + # Ensure no missing mailing lists need creating in this test. + with patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()): + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") + + # Sleep should have been awaited for the two failed attempts. + self.assertEqual(mock_sleep.await_count, 2) + + # No final alert on success. + self.cog._alert_mods_python_news_load_failure.assert_not_awaited() + + # Task should start after successful load. + self.mock_fetch_new_media_start.assert_called_once() + + # State should be populated. + self.assertIn("pep", self.cog.seen_items) + self.assertEqual(self.cog.seen_items["pep"], {"1", "2"}) + + # No posts should happen because no missing lists. + self.bot.api_client.post.assert_not_awaited() + + async def test_retries_max_times_fails_and_alerts(self): + """`cog_load` should alert and re-raise when all retry attempts fail.""" + self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with ( + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(OSError), + ): + await self.cog.cog_load() + + # Should try exactly MAX_ATTEMPTS times. + from bot.exts.info import python_news as python_news_module + + self.assertEqual(self.bot.api_client.get.await_count, python_news_module.MAX_ATTEMPTS) + self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") + + # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. + self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) + + # Alert should be sent once at the end. + self.cog._alert_mods_python_news_load_failure.assert_awaited_once() + + error, attempts = self.cog._alert_mods_python_news_load_failure.await_args.args + self.assertIsInstance(error, OSError) + self.assertEqual(attempts, python_news_module.MAX_ATTEMPTS) + + # Task should never start if load fails. + self.mock_fetch_new_media_start.assert_not_called() + + def test_retryable_python_news_load_error(self): + """`_retryable_site_load_error` should classify temporary failures as retryable.""" + test_cases = ( + (ResponseCodeError(MagicMock(status=408)), True), + (ResponseCodeError(MagicMock(status=429)), True), + (ResponseCodeError(MagicMock(status=500)), True), + (ResponseCodeError(MagicMock(status=503)), True), + (ResponseCodeError(MagicMock(status=400)), False), + (ResponseCodeError(MagicMock(status=404)), False), + (TimeoutError("timeout"), True), + (OSError("os error"), True), + (AttributeError("attr"), False), + (ValueError("value"), False), + ) + + for error, expected_retryable in test_cases: + with self.subTest(error=error): + self.assertEqual(self.cog._retryable_site_load_error(error), expected_retryable) + + async def test_cog_load_does_not_retry_non_retryable_error(self): + """`cog_load` should not retry when the error is non-retryable.""" + # 404 should be considered non-retryable by your predicate. + self.bot.api_client.get.side_effect = ResponseCodeError(MagicMock(status=404)) + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with ( + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(ResponseCodeError), + ): + await self.cog.cog_load() + + self.assertEqual(self.bot.api_client.get.await_count, 1) + self.assertEqual(mock_sleep.await_count, 0) + self.cog._alert_mods_python_news_load_failure.assert_not_awaited() + self.mock_fetch_new_media_start.assert_not_called() From e534601b6e7286131ca39018b83ce0c81d6e0946 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Sun, 1 Mar 2026 14:20:52 +0100 Subject: [PATCH 041/115] refactor: remove local mod alerting and update tests (#16) --- bot/exts/info/python_news.py | 23 --------------------- tests/bot/exts/info/test_python_news.py | 27 +++++++------------------ 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 255d875d45..2dd607a6ed 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -54,28 +54,6 @@ def _retryable_site_load_error(error: Exception) -> bool: return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) - async def _alert_mods_python_news_load_failure( - self, error: Exception, attempts: int - ) -> None: - """Alert moderators if PythonNews fails to load after all retries.""" - channel = self.bot.get_channel(constants.Channels.mod_alerts) - - if channel is None: - log.error("Could not find mod_alerts channel to send PythonNews failure alert.") - return - - status_info = "" - if isinstance(error, ResponseCodeError): - status_info = f" (status {error.status})" - - await channel.send( - ":warning: **PythonNews failed to load.**\n" - f"Attempts: {attempts}\n" - f"Error: `{error.__class__.__name__}{status_info}`\n\n" - "Python news posting will not be active until the bot is restarted " - "or the extension is reloaded." - ) - async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" for attempt in range(1, MAX_ATTEMPTS + 1): @@ -106,7 +84,6 @@ async def cog_load(self) -> None: "Failed to load PythonNews mailing lists after %d attempt(s).", MAX_ATTEMPTS, ) - await self._alert_mods_python_news_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py index 0b47c6d7b3..273e8d5dc8 100644 --- a/tests/bot/exts/info/test_python_news.py +++ b/tests/bot/exts/info/test_python_news.py @@ -40,11 +40,11 @@ async def test_cog_load_retries_then_succeeds(self): ] # Ensure no missing mailing lists need creating in this test. - with patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()): - self.cog._alert_mods_python_news_load_failure = AsyncMock() - - with patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: - await self.cog.cog_load() + with ( + patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()), + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + ): + await self.cog.cog_load() self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") @@ -52,9 +52,6 @@ async def test_cog_load_retries_then_succeeds(self): # Sleep should have been awaited for the two failed attempts. self.assertEqual(mock_sleep.await_count, 2) - # No final alert on success. - self.cog._alert_mods_python_news_load_failure.assert_not_awaited() - # Task should start after successful load. self.mock_fetch_new_media_start.assert_called_once() @@ -65,10 +62,9 @@ async def test_cog_load_retries_then_succeeds(self): # No posts should happen because no missing lists. self.bot.api_client.post.assert_not_awaited() - async def test_retries_max_times_fails_and_alerts(self): - """`cog_load` should alert and re-raise when all retry attempts fail.""" + async def test_retries_max_times_fails_and_reraises(self): + """`cog_load` should re-raise when all retry attempts fail.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - self.cog._alert_mods_python_news_load_failure = AsyncMock() with ( patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, @@ -85,13 +81,6 @@ async def test_retries_max_times_fails_and_alerts(self): # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) - # Alert should be sent once at the end. - self.cog._alert_mods_python_news_load_failure.assert_awaited_once() - - error, attempts = self.cog._alert_mods_python_news_load_failure.await_args.args - self.assertIsInstance(error, OSError) - self.assertEqual(attempts, python_news_module.MAX_ATTEMPTS) - # Task should never start if load fails. self.mock_fetch_new_media_start.assert_not_called() @@ -118,7 +107,6 @@ async def test_cog_load_does_not_retry_non_retryable_error(self): """`cog_load` should not retry when the error is non-retryable.""" # 404 should be considered non-retryable by your predicate. self.bot.api_client.get.side_effect = ResponseCodeError(MagicMock(status=404)) - self.cog._alert_mods_python_news_load_failure = AsyncMock() with ( patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, @@ -128,5 +116,4 @@ async def test_cog_load_does_not_retry_non_retryable_error(self): self.assertEqual(self.bot.api_client.get.await_count, 1) self.assertEqual(mock_sleep.await_count, 0) - self.cog._alert_mods_python_news_load_failure.assert_not_awaited() self.mock_fetch_new_media_start.assert_not_called() From 4e203c2bb0d8b55b183196ac905a9eddbe2782ce Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Sun, 1 Mar 2026 14:53:15 +0100 Subject: [PATCH 042/115] refactor: remove _alert_mods_if_loading_failed() (#14) Remove redundant code and corresponding parts of unit tests. --- .../moderation/infraction/superstarify.py | 24 +------------------ .../infraction/test_superstarify_cog.py | 7 ------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 8139eaa37d..180a49d304 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -4,7 +4,6 @@ import textwrap from pathlib import Path -import discord from discord import Embed, Member from discord.ext.commands import Cog, Context, command, has_any_role from discord.utils import escape_markdown @@ -13,7 +12,7 @@ from bot import constants from bot.bot import Bot -from bot.constants import Icons, URLs +from bot.constants import URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -21,7 +20,6 @@ from bot.log import get_logger from bot.utils import time from bot.utils.messages import format_user -from bot.utils.modlog import send_log_message MAX_RETRY_ATTEMPTS = URLs.connect_max_retries BACKOFF_INITIAL_DELAY = 5 # seconds @@ -250,30 +248,10 @@ async def _fetch_with_retries(self, return await self.bot.api_client.get("bot/infractions", params=params) except Exception as e: if attempt == retries - 1 or not self._check_error_is_retriable(e): - await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) return None - async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - """Alert moderators that loading the superstarify cog failed after retries.""" - message = textwrap.dedent( - f""" - An error occurred while loading the Superstarify Cog, and it failed to initialize properly. - - Error details: - {error} - """ - ) - - await send_log_message( - self.bot, - title="Error: Failed to initialize the Superstarify Cog", - text=message, - ping_everyone=True, - icon_url=Icons.token_removed, - colour=discord.Color.red() - ) async def _check_error_is_retriable(self, error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py index 4e3004ce6b..54473c7064 100644 --- a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -15,7 +15,6 @@ async def asyncSetUp(self): self.bot.api_client = MagicMock() self.bot.api_client.get = AsyncMock() - self.cog._alert_mods_if_loading_failed = AsyncMock() self.cog._check_error_is_retriable = MagicMock(return_value=True) async def test_fetch_from_api_success(self): @@ -32,7 +31,6 @@ async def test_fetch_from_api_success(self): "bot/infractions", params={"user__id": "123"}, ) - self.cog._alert_mods_if_loading_failed.assert_not_called() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_fetch_retries_then_succeeds(self, _): @@ -48,7 +46,6 @@ async def test_fetch_retries_then_succeeds(self, _): self.assertEqual(result, [{"id": 42}]) self.assertEqual(self.bot.api_client.get.await_count, 2) - self.cog._alert_mods_if_loading_failed.assert_not_called() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_fetch_fails_after_max_retries(self, _): @@ -64,7 +61,6 @@ async def test_fetch_fails_after_max_retries(self, _): self.assertEqual(self.bot.api_client.get.await_count, 3) - self.cog._alert_mods_if_loading_failed.assert_awaited_once_with(error) @patch("asyncio.sleep", new_callable=AsyncMock) async def test_non_retriable_error_stops_immediately(self, _): @@ -79,7 +75,6 @@ async def test_non_retriable_error_stops_immediately(self, _): # only one attempt self.bot.api_client.get.assert_awaited_once() - self.cog._alert_mods_if_loading_failed.assert_awaited_once() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_member_update_recovers_from_api_failure(self, _): @@ -108,5 +103,3 @@ async def test_alert_triggered_after_total_failure(self, _): with self.assertRaises(OSError): await self.cog._fetch_with_retries(retries=3) - - self.cog._alert_mods_if_loading_failed.assert_awaited_once() From f8028330db89ac418d63243d97cf3e145792dafb Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Sun, 1 Mar 2026 15:03:35 +0100 Subject: [PATCH 043/115] doc: add effort spent (#7) Added own effort spent on issue --- report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report.md b/report.md index 8755ff53ed..2d5f0dd0d9 100644 --- a/report.md +++ b/report.md @@ -46,7 +46,7 @@ Estimated effort per team member, in hours: | Josef | | | | | | | | | | Alexander | | | | | | | | | | Carl | | | | | | | | | -| Fabian | | | | | | | | | +| Fabian | 8 | 3 | 4 | 5 | 3 | 3 | 1 | ~27 | | Total | | | | | | | | | ### Dependencies and setup tasks: From 695f0ebdc05255c371685f834499a43d9d807d5f Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sun, 1 Mar 2026 15:24:44 +0100 Subject: [PATCH 044/115] docs: describe design patterns and our modifications (Closes #25) --- report.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/report.md b/report.md index 2d5f0dd0d9..5ef41ea655 100644 --- a/report.md +++ b/report.md @@ -141,6 +141,18 @@ Optional (point 1): Architectural overview. Optional (point 2): relation to design pattern(s). +## Design patterns + +The bot follows an *asynchronous event-driven* architecture built on top of asyncio, where the event loop acts as a *Reactor* and the cogs function as *Observers* reacting to events dispatched by Discord. Our update extends this architectural model into the startup phase by making the main bot explicitly await the loading of extensions and cogs. Instead of treating initialization as loosely coordinated asynchronous tasks, startup is now handled as a controlled and deterministic async workflow. + +By centrally awaiting extension loading and registering failures, we effectively introduced a structured lifecycle orchestration mechanism. This resembles the *Template Method pattern*; the bot defines the high-level startup algorithm, while individual extensions provide their specific setup behavior. The core now governs execution order, error propagation, and completion, strengthening architectural cohesion and making startup behavior explicit rather than implicit. + +The introduction of a centralized failure registry and moderator notification system addresses a reliability concern. Previously, most extension failures were handled locally and silently. Now, error notification is abstracted into one central mechanism which ensures consistent handling and visibility. This can be seen as a *refactoring towards separation of concerns* and consolidation of duplicated logic, improving observability and maintainability without altering the *modular extension design*. + +Adding retry logic for critical extensions introduces a resilience pattern into the architecture. Instead of failing permanently, important components now implement retry behavior, aligning with *fault-tolerant design principles*. This change elevates the system from a *fail-fast* startup model to a selectively resilient one, while preserving the modular cog-based structure. + +Overall, the update strengthens the architectural maturity of the system. It centralizes lifecycle control, improves separation of responsibilities between the core and extensions, and introduces structured error handling and resilience patterns, all while remaining consistent with the existing asynchronous event-driven and modular design principles. + ## Overall experience ### What are your main take-aways from this project? What did you learn? From 6e1c8cde19e4b7de74b0560a74691a9a05c0a00e Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sun, 1 Mar 2026 16:21:12 +0100 Subject: [PATCH 045/115] docs: add personal time spent Alexander (#7) --- report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report.md b/report.md index 5ef41ea655..f0b22bbba0 100644 --- a/report.md +++ b/report.md @@ -44,7 +44,7 @@ Estimated effort per team member, in hours: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | Apeel | 8 | 3 | 3 | 6 | 3 | 4 | 1 | ~28 | | Josef | | | | | | | | | -| Alexander | | | | | | | | | +| Alexander | 8 | 2 | 1 | 5 | 2 | 5 | 2 | ~25 | | Carl | | | | | | | | | | Fabian | 8 | 3 | 4 | 5 | 3 | 3 | 1 | ~27 | | Total | | | | | | | | | From 8428c67e321bd2059de31cba9dba5f7997c7f185 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Thu, 26 Feb 2026 11:08:59 +0100 Subject: [PATCH 046/115] test: add tests for invalid extensions and cogs (#3) --- bot/bot.py | 13 ++- tests/bot/exts/test_extensions.py | 157 ++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 tests/bot/exts/test_extensions.py diff --git a/bot/bot.py b/bot/bot.py index 35dbd1ba4e..5e01824ba9 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,5 +1,6 @@ import asyncio import contextlib +import contextvars from sys import exception import aiohttp @@ -13,6 +14,10 @@ log = get_logger("bot") +_current_extension: contextvars.ContextVar[str | None] = contextvars.ContextVar( + "current_extension", default=None +) + class StartupError(Exception): """Exception class for startup errors.""" @@ -26,9 +31,15 @@ class Bot(BotBase): """A subclass of `pydis_core.BotBase` that implements bot-specific functions.""" def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + # Track extension load failures and tasks so we can report them after all attempts have completed + self.extension_load_failures: dict[str, BaseException] = {} + self._extension_load_tasks: dict[str, asyncio.Task] = {} + + + + async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" with start_transaction(op="cog-load", name=name): diff --git a/tests/bot/exts/test_extensions.py b/tests/bot/exts/test_extensions.py new file mode 100644 index 0000000000..2546873883 --- /dev/null +++ b/tests/bot/exts/test_extensions.py @@ -0,0 +1,157 @@ +import asyncio +import contextlib +import importlib +import sys +import unittest +import unittest.mock +from pathlib import Path +from tempfile import TemporaryDirectory + +import discord + +from bot.bot import Bot + + +class ExtensionLoadingTests(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.http_session = unittest.mock.MagicMock(name="http_session") + + # Set up a Bot instance with minimal configuration for testing extension loading. + self.bot = Bot( + command_prefix="!", + guild_id=123456789012345678, + allowed_roles=[], + http_session=self.http_session, + intents=discord.Intents.none() + ) + + # Avoid blocking in _load_extensions() + async def _instant() -> None: + return None + self.bot.wait_until_guild_available = _instant + + # Ensure clean state + self.bot.extension_load_failures = {} + self.bot._extension_load_tasks = {} + + # Temporary importable package: tmp_root/testexts/__init__.py + modules + self._temp_dir = TemporaryDirectory() + self.addCleanup(self._temp_dir.cleanup) + self.tmp_root = Path(self._temp_dir.name) + + self.pkg_name = "testexts" + self.pkg_dir = self.tmp_root / self.pkg_name + self.pkg_dir.mkdir(parents=True, exist_ok=True) + (self.pkg_dir / "__init__.py").write_text("", encoding="utf-8") + + sys.path.insert(0, str(self.tmp_root)) + self.addCleanup(self._remove_tmp_from_syspath) + self._purge_modules(self.pkg_name) + + # Ensure scheduled tasks execute during tests + self._create_task_patcher = unittest.mock.patch( + "pydis_core.utils.scheduling.create_task", + side_effect=lambda coro, *a, **k: asyncio.create_task(coro), + ) + self._create_task_patcher.start() + self.addCleanup(self._create_task_patcher.stop) + + def _remove_tmp_from_syspath(self) -> None: + """Remove the temporary directory from sys.path.""" + with contextlib.suppress(ValueError): + sys.path.remove(str(self.tmp_root)) + + def _purge_modules(self, prefix: str) -> None: + """Remove modules from sys.modules that match the given prefix.""" + for name in list(sys.modules.keys()): + if name == prefix or name.startswith(prefix + "."): + del sys.modules[name] + + def _write_ext(self, module_name: str, source: str) -> str: + """Write an extension module with the given source code and + return its full import path.""" + (self.pkg_dir / f"{module_name}.py").write_text(source, encoding="utf-8") + full = f"{self.pkg_name}.{module_name}" + self._purge_modules(full) + return full + + async def _run_loader(self) -> None: + """Run the extension loader on the package containing our test extensions.""" + module = importlib.import_module(self.pkg_name) + + await self.bot._load_extensions(module) + + # Wait for all extension load tasks to complete so that exceptions are recorded in the bot's state + tasks = getattr(self.bot, "_extension_load_tasks", {}) or {} + if tasks: + await asyncio.gather(*tasks.values(), return_exceptions=True) + + def _assert_failure_recorded_for_extension(self, ext: str) -> None: + """Assert that a failure is recorded for the given extension.""" + if ext in self.bot.extension_load_failures: + return + for key in self.bot.extension_load_failures: + if key.startswith(ext): + return + self.fail( + f"Expected a failure recorded for {ext!r}. " + f"Recorded keys: {sorted(self.bot.extension_load_failures.keys())}" + ) + + async def test_setup_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_setup_fail", + """ +async def setup(bot): + raise RuntimeError("setup failed") +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_cog_load_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_cogload_fail", + """ +from discord.ext import commands + +class BadCog(commands.Cog): + async def cog_load(self): + raise RuntimeError("cog_load failed") + +async def setup(bot): + await bot.add_cog(BadCog()) +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_add_cog_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_addcog_fail", + """ +from discord.ext import commands + +class DupCog(commands.Cog): + pass + +async def setup(bot): + await bot.add_cog(DupCog()) + await bot.add_cog(DupCog()) +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) + + async def test_import_failure_is_captured(self) -> None: + ext = self._write_ext( + "ext_import_fail", + """ +raise RuntimeError("import failed before setup()") + +async def setup(bot): + pass +""", + ) + await self._run_loader() + self._assert_failure_recorded_for_extension(ext) From b5782ab069c711308bcee5c78d2bfdd3dd5d6f36 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Thu, 26 Feb 2026 11:12:53 +0100 Subject: [PATCH 047/115] feat: add centralized exception logging for extensions and cogs (#3) --- bot/bot.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/bot/bot.py b/bot/bot.py index 5e01824ba9..4184c26cc4 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,11 +1,15 @@ import asyncio import contextlib import contextvars +import types from sys import exception import aiohttp from discord.errors import Forbidden +from discord.ext import commands from pydis_core import BotBase +from pydis_core.utils import scheduling +from pydis_core.utils._extensions import walk_extensions from pydis_core.utils.error_handling import handle_forbidden_from_block from sentry_sdk import new_scope, start_transaction @@ -38,7 +42,67 @@ def __init__(self, *args, **kwargs): self._extension_load_tasks: dict[str, asyncio.Task] = {} + async def add_cog(self, cog: commands.Cog) -> None: + """ + Add a cog to the bot with exception handling. + Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, + including the extension name if available. + """ + extension = _current_extension.get() + + try: + await super().add_cog(cog) + log.info(f"Cog successfully loaded: {cog.qualified_name}") + + except BaseException as e: + key = extension or f"(unknown)::{cog.qualified_name}" + self.extension_load_failures[key] = e + + log.exception( + "FAILED during add_cog (extension=%s, cog=%s)", + extension, + cog.qualified_name, + ) + # Propagate error + raise + + async def _load_extensions(self, module: types.ModuleType) -> None: + + log.info("Waiting for guild %d to be available before loading extensions.", self.guild_id) + await self.wait_until_guild_available() + + self.all_extensions = walk_extensions(module) + + async def _load_one(extension: str) -> None: + token = _current_extension.set(extension) + + try: + log.info(f"Loading extension: {extension}") + await self.load_extension(extension) + log.info(f"Loaded extension: {extension}") + + except BaseException as e: + self.extension_load_failures[extension] = e + log.exception("FAILED to load extension: %s", extension) + raise + + finally: + _current_extension.reset(token) + + for extension in self.all_extensions: + task = scheduling.create_task(_load_one(extension)) + self._extension_load_tasks[extension] = task + + # Wait for all load tasks to complete so we can report any failures together + await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) + + if self.extension_load_failures: + log.warning( + "Extension/cog load failures (%d): %s", + len(self.extension_load_failures), + ", ".join(sorted(self.extension_load_failures.keys())), + ) async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" From 50cdacc5915cf0c0ae21d66c2756fdeb6bc56dc8 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Fri, 27 Feb 2026 14:41:47 +0100 Subject: [PATCH 048/115] feat: add startup failure status message logs (#3 #5) --- bot/bot.py | 121 ++++++++++++++++----------------- bot/utils/startup_reporting.py | 61 +++++++++++++++++ 2 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 bot/utils/startup_reporting.py diff --git a/bot/bot.py b/bot/bot.py index 4184c26cc4..3d235da920 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -15,6 +15,7 @@ from bot import constants, exts from bot.log import get_logger +from bot.utils.startup_reporting import StartupFailureReporter log = get_logger("bot") @@ -22,7 +23,6 @@ "current_extension", default=None ) - class StartupError(Exception): """Exception class for startup errors.""" @@ -40,69 +40,7 @@ def __init__(self, *args, **kwargs): # Track extension load failures and tasks so we can report them after all attempts have completed self.extension_load_failures: dict[str, BaseException] = {} self._extension_load_tasks: dict[str, asyncio.Task] = {} - - - async def add_cog(self, cog: commands.Cog) -> None: - """ - Add a cog to the bot with exception handling. - - Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, - including the extension name if available. - """ - extension = _current_extension.get() - - try: - await super().add_cog(cog) - log.info(f"Cog successfully loaded: {cog.qualified_name}") - - except BaseException as e: - key = extension or f"(unknown)::{cog.qualified_name}" - self.extension_load_failures[key] = e - - log.exception( - "FAILED during add_cog (extension=%s, cog=%s)", - extension, - cog.qualified_name, - ) - # Propagate error - raise - - async def _load_extensions(self, module: types.ModuleType) -> None: - - log.info("Waiting for guild %d to be available before loading extensions.", self.guild_id) - await self.wait_until_guild_available() - - self.all_extensions = walk_extensions(module) - - async def _load_one(extension: str) -> None: - token = _current_extension.set(extension) - - try: - log.info(f"Loading extension: {extension}") - await self.load_extension(extension) - log.info(f"Loaded extension: {extension}") - - except BaseException as e: - self.extension_load_failures[extension] = e - log.exception("FAILED to load extension: %s", extension) - raise - - finally: - _current_extension.reset(token) - - for extension in self.all_extensions: - task = scheduling.create_task(_load_one(extension)) - self._extension_load_tasks[extension] = task - - # Wait for all load tasks to complete so we can report any failures together - await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) - - if self.extension_load_failures: - log.warning( - "Extension/cog load failures (%d): %s", - len(self.extension_load_failures), - ", ".join(sorted(self.extension_load_failures.keys())), - ) + self._startup_failure_reporter = StartupFailureReporter() async def load_extension(self, name: str, *args, **kwargs) -> None: """Extend D.py's load_extension function to also record sentry performance stats.""" @@ -152,3 +90,58 @@ async def on_error(self, event: str, *args, **kwargs) -> None: scope.set_extra("kwargs", kwargs) log.exception(f"Unhandled exception in {event}.") + + async def add_cog(self, cog: commands.Cog) -> None: + """ + Add a cog to the bot with exception handling. + + Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, + including the extension name if available. + """ + extension = _current_extension.get() + + try: + await super().add_cog(cog) + log.info(f"Cog successfully loaded: {cog.qualified_name}") + + except BaseException as e: + key = extension or f"(unknown)::{cog.qualified_name}" + self.extension_load_failures[key] = e + + log.exception( + f"Failed during add_cog (extension={extension}, cog={cog.qualified_name})" + ) + # Propagate error + raise + + async def _load_extensions(self, module: types.ModuleType) -> None: + """Load extensions for the bot.""" + await self.wait_until_guild_available() + + self.all_extensions = walk_extensions(module) + + async def _load_one(extension: str) -> None: + token = _current_extension.set(extension) + + try: + await self.load_extension(extension) + log.info(f"Extension successfully loaded: {extension}") + + except BaseException as e: + self.extension_load_failures[extension] = e + log.exception(f"Failed to load extension: {extension}") + raise + + finally: + _current_extension.reset(token) + + for extension in self.all_extensions: + task = scheduling.create_task(_load_one(extension)) + self._extension_load_tasks[extension] = task + + # Wait for all load tasks to complete so we can report any failures together + await asyncio.gather(*self._extension_load_tasks.values(), return_exceptions=True) + + # Send a Discord message to moderators if any extensions failed to load + if self.extension_load_failures : + await self._startup_failure_reporter.notify(self, self.extension_load_failures) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py new file mode 100644 index 0000000000..b3ad78b67f --- /dev/null +++ b/bot/utils/startup_reporting.py @@ -0,0 +1,61 @@ +import textwrap +from collections.abc import Mapping +from dataclasses import dataclass +from typing import TYPE_CHECKING + +import discord + +from bot.constants import Channels, Icons +from bot.log import get_logger + +log = get_logger(__name__) + +if TYPE_CHECKING: + from bot.bot import Bot + +@dataclass(frozen=True) +class StartupFailureReporter: + """Formats and sends one aggregated startup failure alert to moderators.""" + + async def notify(self, bot: Bot, failures: Mapping[str, BaseException], channel_id: int = Channels.mod_log) -> None: + """Notify moderators of startup failures.""" + if not failures: + return + + if bot.get_channel(channel_id) is None: + # Can't send a message if the channel doesn't exist, so log instead + log.warning("Failed to send startup failure report: mod_log channel not found.") + return + + try: + # Local import avoids circular dependency + from bot.utils.modlog import send_log_message + + text = self.render(failures) + + await send_log_message( + bot, + icon_url=Icons.token_removed, + colour=discord.Colour.red(), + title="Startup: Some extensions failed to load", + text=text, + ping_everyone=True, + channel_id=channel_id + ) + except Exception as e: + log.exception(f"Failed to send startup failure report: {e}") + + def render(self, failures: Mapping[str, BaseException]) -> str: + """Render a human-readable message from the given failures.""" + keys = sorted(failures.keys()) + + lines = [] + lines.append("The following extension(s) failed to load:") + for k in keys: + e = failures[k] + lines.append(f"- **{k}** - `{type(e).__name__}: {e}`") + + return textwrap.dedent(f""" + Failed items: + {chr(10).join(lines)} + """).strip() From f22028bfadedd20c09f408c048f92aa03805b366 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 15:50:15 +0100 Subject: [PATCH 049/115] test: add Filtering cog_load and test scaffold and mocked setup --- .../bot/exts/filtering/test_filtering_cog.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/bot/exts/filtering/test_filtering_cog.py diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py new file mode 100644 index 0000000000..2faafa00cd --- /dev/null +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -0,0 +1,28 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.filtering.filtering import Filtering + + +class FilteringCogLoadTests(unittest.IsolatedAsyncioTestCase): + """Test startup behavior of the Filtering cog (`cog_load`).""" + + def setUp(self) -> None: + """Set up a Filtering cog with a mocked bot and stubbed startup dependencies.""" + self.bot = MagicMock() + self.bot.wait_until_guild_available = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + self.cog = Filtering(self.bot) + + # Stub internals that are not relevant to this unit test. + self.cog.collect_loaded_types = MagicMock() + self.cog.schedule_offending_messages_deletion = AsyncMock() + self.cog._fetch_or_generate_filtering_webhook = AsyncMock(return_value=MagicMock()) + + # `weekly_auto_infraction_report_task` is a discord task loop; patch its start method. + self.start_patcher = patch.object(self.cog.weekly_auto_infraction_report_task, "start") + self.mock_weekly_task_start = self.start_patcher.start() + self.addCleanup(self.start_patcher.stop) From f4d398b462ba77b26ee3ea148c31eff8d45ae379 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 15:53:19 +0100 Subject: [PATCH 050/115] test: cover cog_load behavior when filter list fetch fails --- tests/bot/exts/filtering/test_filtering_cog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 2faafa00cd..58b111505a 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -26,3 +26,18 @@ def setUp(self) -> None: self.start_patcher = patch.object(self.cog.weekly_auto_infraction_report_task, "start") self.mock_weekly_task_start = self.start_patcher.start() self.addCleanup(self.start_patcher.stop) + + async def test_cog_load_when_filter_list_fetch_fails(self): + """`cog_load` should currently raise if loading filter lists from the API fails.""" + self.bot.api_client.get.side_effect = RuntimeError("Simulated site/API outage during cog_load") + + with self.assertRaises(RuntimeError): + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + + # Startup should stop before later steps. + self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() + self.cog.schedule_offending_messages_deletion.assert_not_awaited() + self.mock_weekly_task_start.assert_not_called() From 43f1c5b882d8159c5bd03aa93b179c958fa09e1e Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 20:42:22 +0100 Subject: [PATCH 051/115] feat: Add retry error filter and mod alert for filter load failures #6 --- bot/exts/filtering/filtering.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 210ae3fb05..837b552719 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -64,6 +64,8 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday +FILTER_LOAD_MAX_ATTEMPTS = 3 +INITIAL_BACKOFF_SECONDS = 1 async def _extract_text_file_content(att: discord.Attachment) -> str: @@ -122,6 +124,33 @@ async def cog_load(self) -> None: await self.schedule_offending_messages_deletion() self.weekly_auto_infraction_report_task.start() + @staticmethod + def _retryable_filter_load_error(error: Exception) -> bool: + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status == 429 or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + + async def _alert_mods_filter_load_failure(self, error: Exception, attempts: int) -> None: + """Send an alert to mod-alerts when startup fails after all retry attempts.""" + mod_alerts_channel = self.bot.get_channel(Channels.mod_alerts) + if mod_alerts_channel is None: + log.error("Failed to send filtering startup failure alert: #mod-alerts channel is unavailable.") + return + + error_details = f"{error.__class__.__name__}: {error}" + if isinstance(error, ResponseCodeError): + error_details = f"HTTP {error.status} - {error_details}" + + try: + await mod_alerts_channel.send( + ":warning: Filtering failed to load filter lists during startup " + f"after {attempts} attempt(s). Error: `{error_details}`" + ) + except discord.HTTPException: + log.exception("Failed to send filtering startup failure alert to #mod-alerts.") + def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ Subscribe a filter list to the given events. From 8b575f6ecc4a3458468e3c6aaff8db7ced76315f Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Wed, 25 Feb 2026 20:46:25 +0100 Subject: [PATCH 052/115] feat: add retry with exponential backoff for filter list loading #6 --- bot/exts/filtering/filtering.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 837b552719..850fbae110 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -1,3 +1,4 @@ +import asyncio import datetime import io import json @@ -110,7 +111,32 @@ async def cog_load(self) -> None: await self.bot.wait_until_guild_available() log.trace("Loading filtering information from the database.") - raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") + for attempt in range(1, FILTER_LOAD_MAX_ATTEMPTS + 1): + try: + raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") + break + except Exception as error: + is_retryable = self._retryable_filter_load_error(error) + is_last_attempt = attempt == FILTER_LOAD_MAX_ATTEMPTS + + if not is_retryable: + raise + + if is_last_attempt: + log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) + await self._alert_mods_filter_load_failure(error, attempt) + raise + + backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + log.warning( + "Failed to load filtering data (attempt %d/%d). Retrying in %d second(s): %s", + attempt, + FILTER_LOAD_MAX_ATTEMPTS, + backoff_seconds, + error + ) + await asyncio.sleep(backoff_seconds) + example_list = None for raw_filter_list in raw_filter_lists: loaded_list = self._load_raw_filter_list(raw_filter_list) From 2cbb226a769b1f1a38158571cb3c40497124f4f1 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:09:48 +0100 Subject: [PATCH 053/115] fix: updated untracked unit test --- tests/bot/exts/filtering/test_filtering_cog.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 58b111505a..f708cadea0 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -29,7 +29,7 @@ def setUp(self) -> None: async def test_cog_load_when_filter_list_fetch_fails(self): """`cog_load` should currently raise if loading filter lists from the API fails.""" - self.bot.api_client.get.side_effect = RuntimeError("Simulated site/API outage during cog_load") + self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") with self.assertRaises(RuntimeError): await self.cog.cog_load() @@ -41,3 +41,16 @@ async def test_cog_load_when_filter_list_fetch_fails(self): self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() self.cog.schedule_offending_messages_deletion.assert_not_awaited() self.mock_weekly_task_start.assert_not_called() + + async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): + """`cog_load` should continue startup when the API returns filter lists successfully.""" + self.bot.api_client.get.return_value = [] + + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() + self.cog.collect_loaded_types.assert_called_once_with(None) + self.cog.schedule_offending_messages_deletion.assert_awaited_once() + self.mock_weekly_task_start.assert_called_once() From f10d6fa134e306436a9a222a287e376d223af55e Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:32:47 +0100 Subject: [PATCH 054/115] fix: added filtering cog retry test and fixed cog_load startup tests #13 --- .../bot/exts/filtering/test_filtering_cog.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index f708cadea0..45dbe23e2f 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -1,6 +1,8 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from pydis_core.site_api import ResponseCodeError + from bot.exts.filtering.filtering import Filtering @@ -30,12 +32,16 @@ def setUp(self) -> None: async def test_cog_load_when_filter_list_fetch_fails(self): """`cog_load` should currently raise if loading filter lists from the API fails.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") + mock_alerts_channel = MagicMock() + mock_alerts_channel.send = AsyncMock() + self.bot.get_channel.return_value = mock_alerts_channel - with self.assertRaises(RuntimeError): + with self.assertRaises(OSError): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() - self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() @@ -49,8 +55,27 @@ async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() - self.bot.api_client.get.assert_awaited_once_with("bot/filter/filter_lists") + self.assertEqual(self.bot.api_client.get.await_count, 1) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() self.cog.collect_loaded_types.assert_called_once_with(None) self.cog.schedule_offending_messages_deletion.assert_awaited_once() self.mock_weekly_task_start.assert_called_once() + + def test_retryable_filter_load_error(self): + """`_retryable_filter_load_error` should classify temporary failures as retryable.""" + test_cases = ( + (ResponseCodeError(MagicMock(status=429)), True), + (ResponseCodeError(MagicMock(status=500)), True), + (ResponseCodeError(MagicMock(status=503)), True), + (ResponseCodeError(MagicMock(status=400)), False), + (ResponseCodeError(MagicMock(status=404)), False), + (TimeoutError("timeout"), True), + (OSError("os error"), True), + (AttributeError("attr"), False), + (ValueError("value"), False), + ) + + for error, expected_retryable in test_cases: + with self.subTest(error=error): + self.assertEqual(self.cog._retryable_filter_load_error(error), expected_retryable) From 92fc44216f43f68905ea9b6db6ba5520b556a397 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:52:10 +0100 Subject: [PATCH 055/115] test: Added filtering cog retry-path tests for success and final-failure alerting #13 --- .../bot/exts/filtering/test_filtering_cog.py | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 45dbe23e2f..02c1ecfa41 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -29,39 +29,54 @@ def setUp(self) -> None: self.mock_weekly_task_start = self.start_patcher.start() self.addCleanup(self.start_patcher.stop) - async def test_cog_load_when_filter_list_fetch_fails(self): - """`cog_load` should currently raise if loading filter lists from the API fails.""" + async def test_cog_load_retries_then_succeeds(self): + """`cog_load` should retry temporary failures and complete startup after a successful fetch.""" + self.bot.api_client.get.side_effect = [ + OSError("temporary outage"), + TimeoutError("temporary timeout"), + [], + ] + self.cog._alert_mods_filter_load_failure = AsyncMock() + + with patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + + self.bot.wait_until_guild_available.assert_awaited_once() + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") + self.assertEqual(mock_sleep.await_count, 2) + self.cog._alert_mods_filter_load_failure.assert_not_awaited() + self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() + self.cog.collect_loaded_types.assert_called_once_with(None) + self.cog.schedule_offending_messages_deletion.assert_awaited_once() + self.mock_weekly_task_start.assert_called_once() + + async def test_retries_three_times_fails_and_alerts(self): + """`cog_load` should alert and re-raise when all retry attempts fail.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - mock_alerts_channel = MagicMock() - mock_alerts_channel.send = AsyncMock() - self.bot.get_channel.return_value = mock_alerts_channel + self.cog._alert_mods_filter_load_failure = AsyncMock() - with self.assertRaises(OSError): + with ( + patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(OSError), + ): await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") + self.assertEqual(mock_sleep.await_count, 2) + self.cog._alert_mods_filter_load_failure.assert_awaited_once() + + error, attempts = self.cog._alert_mods_filter_load_failure.await_args.args + self.assertIsInstance(error, OSError) + self.assertEqual(attempts, 3) # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() self.cog.schedule_offending_messages_deletion.assert_not_awaited() self.mock_weekly_task_start.assert_not_called() - async def test_cog_load_completes_when_filter_list_fetch_succeeds(self): - """`cog_load` should continue startup when the API returns filter lists successfully.""" - self.bot.api_client.get.return_value = [] - - await self.cog.cog_load() - - self.bot.wait_until_guild_available.assert_awaited_once() - self.assertEqual(self.bot.api_client.get.await_count, 1) - self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") - self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() - self.cog.collect_loaded_types.assert_called_once_with(None) - self.cog.schedule_offending_messages_deletion.assert_awaited_once() - self.mock_weekly_task_start.assert_called_once() - def test_retryable_filter_load_error(self): """`_retryable_filter_load_error` should classify temporary failures as retryable.""" test_cases = ( From 71bf092ce09a25fc49216ebce4f52eb6739e816d Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 11:58:20 +0100 Subject: [PATCH 056/115] fix: updated filter_load_max_attempts contant to reuse connect_max_retries from contants.py #13 --- bot/exts/filtering/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 850fbae110..0bb297495f 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -65,7 +65,7 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday -FILTER_LOAD_MAX_ATTEMPTS = 3 +FILTER_LOAD_MAX_ATTEMPTS = constants.URLs.connect_max_retries INITIAL_BACKOFF_SECONDS = 1 From 0f81c172af46f705058d37c5c79a99138c9df641 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Thu, 26 Feb 2026 12:02:16 +0100 Subject: [PATCH 057/115] fix: added 408: Request Timeout Error, to imply for retryable and it's unit test #13 --- bot/exts/filtering/filtering.py | 2 +- tests/bot/exts/filtering/test_filtering_cog.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 0bb297495f..5937b4f4d8 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -154,7 +154,7 @@ async def cog_load(self) -> None: def _retryable_filter_load_error(error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): - return error.status == 429 or error.status >= 500 + return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 02c1ecfa41..5299f58b81 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -80,6 +80,7 @@ async def test_retries_three_times_fails_and_alerts(self): def test_retryable_filter_load_error(self): """`_retryable_filter_load_error` should classify temporary failures as retryable.""" test_cases = ( + (ResponseCodeError(MagicMock(status=408)), True), (ResponseCodeError(MagicMock(status=429)), True), (ResponseCodeError(MagicMock(status=500)), True), (ResponseCodeError(MagicMock(status=503)), True), From feff4b21d9cb81d867c541bfeb9f030fdb87b7bd Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sat, 28 Feb 2026 18:31:12 +0100 Subject: [PATCH 058/115] fix: remove duplicate moderator notification in filters extension (#3) --- bot/exts/filtering/filtering.py | 1 - .../bot/exts/filtering/test_filtering_cog.py | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 5937b4f4d8..d9cb2c504b 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -124,7 +124,6 @@ async def cog_load(self) -> None: if is_last_attempt: log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) - await self._alert_mods_filter_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 5299f58b81..f9d8536f42 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -51,26 +51,29 @@ async def test_cog_load_retries_then_succeeds(self): self.cog.schedule_offending_messages_deletion.assert_awaited_once() self.mock_weekly_task_start.assert_called_once() - async def test_retries_three_times_fails_and_alerts(self): - """`cog_load` should alert and re-raise when all retry attempts fail.""" - self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - self.cog._alert_mods_filter_load_failure = AsyncMock() + async def test_retries_three_times_fails_and_reraises(self): + """`cog_load` should retry and re-raise when all retry attempts fail.""" + self.bot.api_client.get.side_effect = OSError( + "Simulated site/API outage during cog_load" + ) - with ( - patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, - self.assertRaises(OSError), - ): + with patch( + "bot.exts.filtering.filtering.asyncio.sleep", + new_callable=AsyncMock, + ) as mock_sleep, self.assertRaises(OSError) as ctx: await self.cog.cog_load() + self.assertIs(ctx.exception, self.bot.api_client.get.side_effect) + + # Waited for guild availability self.bot.wait_until_guild_available.assert_awaited_once() + + # 3 attempts self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") - self.assertEqual(mock_sleep.await_count, 2) - self.cog._alert_mods_filter_load_failure.assert_awaited_once() - error, attempts = self.cog._alert_mods_filter_load_failure.await_args.args - self.assertIsInstance(error, OSError) - self.assertEqual(attempts, 3) + # Backoff between attempts (attempts - 1) + self.assertEqual(mock_sleep.await_count, 2) # Startup should stop before later steps. self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() From e38f365392ff8a44fd5c23eda38c03caec01e15c Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 11:14:29 +0100 Subject: [PATCH 059/115] feat: add backoff retry loading (#15) --- bot/exts/utils/reminders.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 1b386ec000..7b78b30ef8 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -1,3 +1,4 @@ +import asyncio import random import textwrap import typing as t @@ -44,6 +45,8 @@ # the 2000-character message limit. MAXIMUM_REMINDER_MENTION_OPT_INS = 80 +# setup constants when loading +MAX_RETRY_ATTEMPTS = 3 Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -223,12 +226,23 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" + delay = 5 # seconds await self.bot.wait_until_guild_available() - response = await self.bot.api_client.get( - "bot/reminders", - params={"active": "true"} - ) - + for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): + try: + # response either throws, or is a list of reminders (possibly empty) + response = await self.bot.api_client.get( + "bot/reminders", + params={"active": "true"} + ) + break + except Exception as e: + log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") + if attempt == MAX_RETRY_ATTEMPTS: + log.error("Max retry attempts reached. Failed to load reminders.") + raise + await asyncio.sleep(delay) + delay *= 2 # exponential backoff now = datetime.now(UTC) for reminder in response: From e098a665e14fde1e42162e87ef1716885a853a23 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 11:37:22 +0100 Subject: [PATCH 060/115] feat: alert mods through discord (#15) Alerts the moderators through a discord error message if the loading of the Reminders Cog has failed. --- bot/exts/utils/reminders.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 7b78b30ef8..8a37972aab 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -33,6 +33,7 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg from bot.utils.messages import send_denial +from bot.utils.modlog import send_log_message log = get_logger(__name__) @@ -240,11 +241,11 @@ async def cog_load(self) -> None: log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") + await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(delay) delay *= 2 # exponential backoff now = datetime.now(UTC) - for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) if not is_valid: @@ -258,6 +259,25 @@ async def cog_load(self) -> None: else: self.schedule_reminder(reminder) + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: + message = textwrap.dedent( + f""" + An error occurred while loading the Reminders Cog, and it failed to initialize properly. + + Error details: + {error} + """ + ) + + await send_log_message( + self.bot, + title="Error: Failed to initialize the Reminders Cog", + text=message, + ping_everyone=True, + icon_url=Icons.token_removed, + colour=discord.Color.red() + ) + def ensure_valid_reminder(self, reminder: dict) -> tuple[bool, discord.TextChannel]: """Ensure reminder channel can be fetched otherwise delete the reminder.""" channel = self.bot.get_channel(reminder["channel_id"]) From 4754f19b34851f75f6feb0ab8d0d515c602cae38 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 12:05:14 +0100 Subject: [PATCH 061/115] test: add test skeleton and valid load test (#15) --- tests/bot/exts/utils/test_reminders.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/bot/exts/utils/test_reminders.py diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py new file mode 100644 index 0000000000..f00952722d --- /dev/null +++ b/tests/bot/exts/utils/test_reminders.py @@ -0,0 +1,31 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.utils.reminders import Reminders +from tests.helpers import MockBot + + +class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): + """ Tests startup behaviour of the Reminders cog. """ + + def setUp(self): + self.bot = MockBot() + self.bot.wait_until_guild_available = AsyncMock() + self.cog = Reminders(self.bot) + + self.cog._alert_mods_if_loading_failed = AsyncMock() + self.cog.ensure_valid_reminder = MagicMock(return_value=(False, None)) + self.cog.schedule_reminder = MagicMock() + self.cog._alert_mods_if_loading_failed = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + @patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) + async def test_reminders_cog_loads(self, sleep_mock): + """ Tests if the Reminders cog loads without error if the GET requests works. """ + self.bot.api_client.get.return_value = [] + try: + await self.cog.cog_load() + except Exception as e: + self.fail(f"Reminders cog failed to load with exception: {e}") From 6d9fc0cd56883b74279587863827fa69e4cf403d Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 15:55:31 +0100 Subject: [PATCH 062/115] test: test retry functionality (#15) --- bot/exts/utils/reminders.py | 8 +++---- tests/bot/exts/utils/test_reminders.py | 30 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 8a37972aab..37eaf58f3d 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -24,6 +24,7 @@ POSITIVE_REPLIES, Roles, STAFF_AND_COMMUNITY_ROLES, + URLs, ) from bot.converters import Duration, UnambiguousUser from bot.errors import LockedResourceError @@ -47,7 +48,8 @@ MAXIMUM_REMINDER_MENTION_OPT_INS = 80 # setup constants when loading -MAX_RETRY_ATTEMPTS = 3 +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries +BACKOFF_INITIAL_DELAY = 5 # seconds Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -227,7 +229,6 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" - delay = 5 # seconds await self.bot.wait_until_guild_available() for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): try: @@ -243,8 +244,7 @@ async def cog_load(self) -> None: log.error("Max retry attempts reached. Failed to load reminders.") await self._alert_mods_if_loading_failed(e) raise - await asyncio.sleep(delay) - delay *= 2 # exponential backoff + await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index f00952722d..8dfa43064a 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -1,9 +1,12 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from bot.constants import URLs from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries + class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): """ Tests startup behaviour of the Reminders cog. """ @@ -21,11 +24,32 @@ def setUp(self): self.bot.api_client = MagicMock() self.bot.api_client.get = AsyncMock() - @patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) - async def test_reminders_cog_loads(self, sleep_mock): + async def test_reminders_cog_loads_correctly(self): """ Tests if the Reminders cog loads without error if the GET requests works. """ self.bot.api_client.get.return_value = [] try: - await self.cog.cog_load() + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock): + await self.cog.cog_load() except Exception as e: self.fail(f"Reminders cog failed to load with exception: {e}") + + async def test_reminders_cog_load_retries_after_initial_exception(self): + """ Tests if the Reminders cog loads after retrying on initial exception. """ + self.bot.api_client.get.side_effect = [Exception("fail 1"), Exception("fail 2"), []] + try: + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + except Exception as e: + self.fail(f"Reminders cog failed to load after retrying with exception: {e}") + self.assertEqual(mock_sleep.await_count, 2) + self.bot.api_client.get.assert_called() + + async def test_reminders_cog_load_fails_after_max_retries(self): + """ Tests if the Reminders cog fails to load after max retries. """ + self.bot.api_client.get.side_effect = Exception("fail") + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing + self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) + self.bot.api_client.get.assert_called() + self.cog._alert_mods_if_loading_failed.assert_called_once() From e1c3796697b83dd5ac5010df030a5ffc58cee00b Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 15:58:52 +0100 Subject: [PATCH 063/115] fix: rewrite test to pass checks (#15) --- tests/bot/exts/utils/test_reminders.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 8dfa43064a..1117efc63b 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -46,9 +46,11 @@ async def test_reminders_cog_load_retries_after_initial_exception(self): async def test_reminders_cog_load_fails_after_max_retries(self): """ Tests if the Reminders cog fails to load after max retries. """ - self.bot.api_client.get.side_effect = Exception("fail") - with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: - await self.cog.cog_load() + self.bot.api_client.get.side_effect = RuntimeError("fail") + with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, \ + self.assertRaises(RuntimeError): + await self.cog.cog_load() + # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) self.bot.api_client.get.assert_called() From 49e96398f2b76cc8ca6d42616cb621f0901b4efe Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 16:03:53 +0100 Subject: [PATCH 064/115] docs: add comments in the cog_load function (#15) --- bot/exts/utils/reminders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 37eaf58f3d..558560b8ad 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -230,6 +230,7 @@ async def cog_unload(self) -> None: async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" await self.bot.wait_until_guild_available() + # retry fetching reminders with exponential backoff for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): try: # response either throws, or is a list of reminders (possibly empty) From abaccdfb9cbb5c25cd0a2ef7046bf8662f22e764 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Thu, 26 Feb 2026 18:05:57 +0100 Subject: [PATCH 065/115] fix: check if the error is retriable (#15) --- bot/exts/utils/reminders.py | 11 +++++++++++ tests/bot/exts/utils/test_reminders.py | 9 ++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 558560b8ad..4f3cc31e59 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -240,6 +240,10 @@ async def cog_load(self) -> None: ) break except Exception as e: + if not self._check_error_is_retriable(e): + log.error(f"Failed to load reminders due to non-retryable error: {e}") + await self._alert_mods_if_loading_failed(e) + raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") @@ -279,6 +283,13 @@ async def _alert_mods_if_loading_failed(self, error: Exception) -> None: colour=discord.Color.red() ) + def _check_error_is_retriable(self, error: Exception) -> bool: + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status in (408, 429) or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + def ensure_valid_reminder(self, reminder: dict) -> tuple[bool, discord.TextChannel]: """Ensure reminder channel can be fetched otherwise delete the reminder.""" channel = self.bot.get_channel(reminder["channel_id"]) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 1117efc63b..04c633cb8f 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -1,6 +1,8 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +from pydis_core.site_api import ResponseCodeError + from bot.constants import URLs from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot @@ -35,7 +37,7 @@ async def test_reminders_cog_loads_correctly(self): async def test_reminders_cog_load_retries_after_initial_exception(self): """ Tests if the Reminders cog loads after retrying on initial exception. """ - self.bot.api_client.get.side_effect = [Exception("fail 1"), Exception("fail 2"), []] + self.bot.api_client.get.side_effect = [OSError("fail1"), OSError("fail2"), []] try: with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: await self.cog.cog_load() @@ -46,9 +48,10 @@ async def test_reminders_cog_load_retries_after_initial_exception(self): async def test_reminders_cog_load_fails_after_max_retries(self): """ Tests if the Reminders cog fails to load after max retries. """ - self.bot.api_client.get.side_effect = RuntimeError("fail") + self.bot.api_client.get.side_effect = ResponseCodeError(response=MagicMock(status=500), + response_text="Internal Server Error") with patch("bot.exts.utils.reminders.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, \ - self.assertRaises(RuntimeError): + self.assertRaises(ResponseCodeError): await self.cog.cog_load() # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing From 673d486e3fb6f66ab9f7e395a7d170c133e6e2bd Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sat, 28 Feb 2026 18:56:20 +0100 Subject: [PATCH 066/115] fix: remove duplicate moderator notification in reminders extension (#3) --- bot/exts/utils/reminders.py | 22 ---------------------- tests/bot/exts/utils/test_reminders.py | 1 - 2 files changed, 23 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 4f3cc31e59..86ab17c76e 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -34,7 +34,6 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check from bot.utils.lock import lock_arg from bot.utils.messages import send_denial -from bot.utils.modlog import send_log_message log = get_logger(__name__) @@ -242,12 +241,10 @@ async def cog_load(self) -> None: except Exception as e: if not self._check_error_is_retriable(e): log.error(f"Failed to load reminders due to non-retryable error: {e}") - await self._alert_mods_if_loading_failed(e) raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") if attempt == MAX_RETRY_ATTEMPTS: log.error("Max retry attempts reached. Failed to load reminders.") - await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) @@ -264,25 +261,6 @@ async def cog_load(self) -> None: else: self.schedule_reminder(reminder) - async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - message = textwrap.dedent( - f""" - An error occurred while loading the Reminders Cog, and it failed to initialize properly. - - Error details: - {error} - """ - ) - - await send_log_message( - self.bot, - title="Error: Failed to initialize the Reminders Cog", - text=message, - ping_everyone=True, - icon_url=Icons.token_removed, - colour=discord.Color.red() - ) - def _check_error_is_retriable(self, error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 04c633cb8f..67c8904fba 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -57,4 +57,3 @@ async def test_reminders_cog_load_fails_after_max_retries(self): # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) self.bot.api_client.get.assert_called() - self.cog._alert_mods_if_loading_failed.assert_called_once() From 97cdf38ec4c2fcbe1415b67c40b24b2cbfeddf37 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 11:56:56 +0100 Subject: [PATCH 067/115] feat: add helper for checking if error is retryable (#16) --- bot/exts/info/python_news.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index c786a9d192..6478eed3d1 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -15,6 +15,9 @@ from bot.log import get_logger from bot.utils.webhooks import send_webhook +PYTHON_NEWS_LOAD_MAX_ATTEMPTS = 3 +PYTHON_NEWS_INITIAL_BACKOFF_SECONDS = 1 + PEPS_RSS_URL = "https://peps.python.org/peps.rss" RECENT_THREADS_TEMPLATE = "https://mail.python.org/archives/list/{name}@python.org/recent-threads" @@ -44,6 +47,12 @@ def __init__(self, bot: Bot): self.webhook: discord.Webhook | None = None self.seen_items: dict[str, set[str]] = {} + @staticmethod + def _retryable_site_load_error(error: Exception) -> bool: + if isinstance(error, ResponseCodeError): + return error.status == 429 or error.status >= 500 + return isinstance(error, (TimeoutError, OSError)) + async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" with sentry_sdk.start_span(description="Fetch mailing lists from site"): From 972d2981fb685a76d0c3b6adc6e8e50abdcecd6b Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 15:53:11 +0100 Subject: [PATCH 068/115] feat: add retry logic to python_news (#16) Changed cog_load() function to retry connecting to api if it fails initially with an exponential delay and limited max attempts. --- bot/exts/info/python_news.py | 57 ++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 6478eed3d1..53873d5d5c 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -1,3 +1,4 @@ +import asyncio import re import typing as t from datetime import UTC, datetime, timedelta @@ -15,8 +16,8 @@ from bot.log import get_logger from bot.utils.webhooks import send_webhook -PYTHON_NEWS_LOAD_MAX_ATTEMPTS = 3 -PYTHON_NEWS_INITIAL_BACKOFF_SECONDS = 1 +MAX_ATTEMPTS = constants.URLs.connect_max_retries +INITIAL_BACKOFF_SECONDS = 1 PEPS_RSS_URL = "https://peps.python.org/peps.rss" @@ -55,19 +56,45 @@ def _retryable_site_load_error(error: Exception) -> bool: async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" - with sentry_sdk.start_span(description="Fetch mailing lists from site"): - response = await self.bot.api_client.get("bot/mailing-lists") - - for mailing_list in response: - self.seen_items[mailing_list["name"]] = set(mailing_list["seen_items"]) - - with sentry_sdk.start_span(description="Update site with new mailing lists"): - for mailing_list in ("pep", *constants.PythonNews.mail_lists): - if mailing_list not in self.seen_items: - await self.bot.api_client.post("bot/mailing-lists", json={"name": mailing_list}) - self.seen_items[mailing_list] = set() - - self.fetch_new_media.start() + for attempt in range(1, MAX_ATTEMPTS + 1): + try: + with sentry_sdk.start_span(description="Fetch mailing lists from site"): + response = await self.bot.api_client.get("bot/mailing-lists") + + # Rebuild state on each successful fetch (avoid partial state across retries) + self.seen_items = {} + for mailing_list in response: + self.seen_items[mailing_list["name"]] = set(mailing_list["seen_items"]) + + with sentry_sdk.start_span(description="Update site with new mailing lists"): + for mailing_list in ("pep", *constants.PythonNews.mail_lists): + if mailing_list not in self.seen_items: + await self.bot.api_client.post("bot/mailing-lists", json={"name": mailing_list}) + self.seen_items[mailing_list] = set() + + self.fetch_new_media.start() + return + + except Exception as error: + if not self._retryable_site_load_error(error): + raise + + if attempt == MAX_ATTEMPTS: + log.exception( + "Failed to load PythonNews mailing lists after %d attempt(s).", + MAX_ATTEMPTS, + ) + raise + + backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + log.warning( + "Failed to load PythonNews mailing lists (attempt %d/%d). Retrying in %d second(s). Error: %s", + attempt, + MAX_ATTEMPTS, + backoff_seconds, + error, + ) + await asyncio.sleep(backoff_seconds) async def cog_unload(self) -> None: """Stop news posting tasks on cog unload.""" From 015927e12e8c3f209639f4bd118b40921a73eef2 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Thu, 26 Feb 2026 18:06:39 +0100 Subject: [PATCH 069/115] feat: alert moderators after max attempts (#16) --- bot/exts/info/python_news.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 53873d5d5c..255d875d45 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -51,9 +51,31 @@ def __init__(self, bot: Bot): @staticmethod def _retryable_site_load_error(error: Exception) -> bool: if isinstance(error, ResponseCodeError): - return error.status == 429 or error.status >= 500 + return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) + async def _alert_mods_python_news_load_failure( + self, error: Exception, attempts: int + ) -> None: + """Alert moderators if PythonNews fails to load after all retries.""" + channel = self.bot.get_channel(constants.Channels.mod_alerts) + + if channel is None: + log.error("Could not find mod_alerts channel to send PythonNews failure alert.") + return + + status_info = "" + if isinstance(error, ResponseCodeError): + status_info = f" (status {error.status})" + + await channel.send( + ":warning: **PythonNews failed to load.**\n" + f"Attempts: {attempts}\n" + f"Error: `{error.__class__.__name__}{status_info}`\n\n" + "Python news posting will not be active until the bot is restarted " + "or the extension is reloaded." + ) + async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" for attempt in range(1, MAX_ATTEMPTS + 1): @@ -84,6 +106,7 @@ async def cog_load(self) -> None: "Failed to load PythonNews mailing lists after %d attempt(s).", MAX_ATTEMPTS, ) + await self._alert_mods_python_news_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) From 2159edb7f73e06da7229f7d6761f5bfe062a8c5d Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Sat, 28 Feb 2026 21:22:49 +0100 Subject: [PATCH 070/115] test: mocked setup and pythonNews tests (#16) --- tests/bot/exts/info/test_python_news.py | 132 ++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/bot/exts/info/test_python_news.py diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py new file mode 100644 index 0000000000..0b47c6d7b3 --- /dev/null +++ b/tests/bot/exts/info/test_python_news.py @@ -0,0 +1,132 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from pydis_core.site_api import ResponseCodeError + +from bot.exts.info.python_news import PythonNews + + +class PythonNewsCogLoadTests(unittest.IsolatedAsyncioTestCase): + """Test startup behavior of the PythonNews cog (`cog_load`).""" + + def setUp(self) -> None: + """Set up a PythonNews cog with a mocked bot and stubbed startup dependencies.""" + self.bot = MagicMock() + self.bot.wait_until_guild_available = AsyncMock() + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + self.bot.api_client.post = AsyncMock() + + # Required by `fetch_new_media` later, but not used in these tests. + self.bot.http_session = MagicMock() + + self.cog = PythonNews(self.bot) + + # Stub out task-loop start, so it doesn't actually schedule anything. + self.start_patcher = patch.object(self.cog.fetch_new_media, "start") + self.mock_fetch_new_media_start = self.start_patcher.start() + self.addCleanup(self.start_patcher.stop) + + async def test_cog_load_retries_then_succeeds(self): + """`cog_load` should retry temporary failures and complete startup after a successful fetch.""" + # First two attempts fail with retryable errors, third succeeds. + self.bot.api_client.get.side_effect = [ + OSError("temporary outage"), + TimeoutError("temporary timeout"), + [ + {"name": "pep", "seen_items": ["1", "2"]}, + ], + ] + + # Ensure no missing mailing lists need creating in this test. + with patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()): + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + await self.cog.cog_load() + + self.assertEqual(self.bot.api_client.get.await_count, 3) + self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") + + # Sleep should have been awaited for the two failed attempts. + self.assertEqual(mock_sleep.await_count, 2) + + # No final alert on success. + self.cog._alert_mods_python_news_load_failure.assert_not_awaited() + + # Task should start after successful load. + self.mock_fetch_new_media_start.assert_called_once() + + # State should be populated. + self.assertIn("pep", self.cog.seen_items) + self.assertEqual(self.cog.seen_items["pep"], {"1", "2"}) + + # No posts should happen because no missing lists. + self.bot.api_client.post.assert_not_awaited() + + async def test_retries_max_times_fails_and_alerts(self): + """`cog_load` should alert and re-raise when all retry attempts fail.""" + self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with ( + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(OSError), + ): + await self.cog.cog_load() + + # Should try exactly MAX_ATTEMPTS times. + from bot.exts.info import python_news as python_news_module + + self.assertEqual(self.bot.api_client.get.await_count, python_news_module.MAX_ATTEMPTS) + self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") + + # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. + self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) + + # Alert should be sent once at the end. + self.cog._alert_mods_python_news_load_failure.assert_awaited_once() + + error, attempts = self.cog._alert_mods_python_news_load_failure.await_args.args + self.assertIsInstance(error, OSError) + self.assertEqual(attempts, python_news_module.MAX_ATTEMPTS) + + # Task should never start if load fails. + self.mock_fetch_new_media_start.assert_not_called() + + def test_retryable_python_news_load_error(self): + """`_retryable_site_load_error` should classify temporary failures as retryable.""" + test_cases = ( + (ResponseCodeError(MagicMock(status=408)), True), + (ResponseCodeError(MagicMock(status=429)), True), + (ResponseCodeError(MagicMock(status=500)), True), + (ResponseCodeError(MagicMock(status=503)), True), + (ResponseCodeError(MagicMock(status=400)), False), + (ResponseCodeError(MagicMock(status=404)), False), + (TimeoutError("timeout"), True), + (OSError("os error"), True), + (AttributeError("attr"), False), + (ValueError("value"), False), + ) + + for error, expected_retryable in test_cases: + with self.subTest(error=error): + self.assertEqual(self.cog._retryable_site_load_error(error), expected_retryable) + + async def test_cog_load_does_not_retry_non_retryable_error(self): + """`cog_load` should not retry when the error is non-retryable.""" + # 404 should be considered non-retryable by your predicate. + self.bot.api_client.get.side_effect = ResponseCodeError(MagicMock(status=404)) + self.cog._alert_mods_python_news_load_failure = AsyncMock() + + with ( + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + self.assertRaises(ResponseCodeError), + ): + await self.cog.cog_load() + + self.assertEqual(self.bot.api_client.get.await_count, 1) + self.assertEqual(mock_sleep.await_count, 0) + self.cog._alert_mods_python_news_load_failure.assert_not_awaited() + self.mock_fetch_new_media_start.assert_not_called() From cd46116d33dfee39f58e1a3eeb6ce38e9d3ca015 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Sun, 1 Mar 2026 14:20:52 +0100 Subject: [PATCH 071/115] refactor: remove local mod alerting and update tests (#16) --- bot/exts/info/python_news.py | 23 --------------------- tests/bot/exts/info/test_python_news.py | 27 +++++++------------------ 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 255d875d45..2dd607a6ed 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -54,28 +54,6 @@ def _retryable_site_load_error(error: Exception) -> bool: return error.status in (408, 429) or error.status >= 500 return isinstance(error, (TimeoutError, OSError)) - async def _alert_mods_python_news_load_failure( - self, error: Exception, attempts: int - ) -> None: - """Alert moderators if PythonNews fails to load after all retries.""" - channel = self.bot.get_channel(constants.Channels.mod_alerts) - - if channel is None: - log.error("Could not find mod_alerts channel to send PythonNews failure alert.") - return - - status_info = "" - if isinstance(error, ResponseCodeError): - status_info = f" (status {error.status})" - - await channel.send( - ":warning: **PythonNews failed to load.**\n" - f"Attempts: {attempts}\n" - f"Error: `{error.__class__.__name__}{status_info}`\n\n" - "Python news posting will not be active until the bot is restarted " - "or the extension is reloaded." - ) - async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" for attempt in range(1, MAX_ATTEMPTS + 1): @@ -106,7 +84,6 @@ async def cog_load(self) -> None: "Failed to load PythonNews mailing lists after %d attempt(s).", MAX_ATTEMPTS, ) - await self._alert_mods_python_news_load_failure(error, attempt) raise backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py index 0b47c6d7b3..273e8d5dc8 100644 --- a/tests/bot/exts/info/test_python_news.py +++ b/tests/bot/exts/info/test_python_news.py @@ -40,11 +40,11 @@ async def test_cog_load_retries_then_succeeds(self): ] # Ensure no missing mailing lists need creating in this test. - with patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()): - self.cog._alert_mods_python_news_load_failure = AsyncMock() - - with patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: - await self.cog.cog_load() + with ( + patch("bot.exts.info.python_news.constants.PythonNews.mail_lists", new=()), + patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + ): + await self.cog.cog_load() self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") @@ -52,9 +52,6 @@ async def test_cog_load_retries_then_succeeds(self): # Sleep should have been awaited for the two failed attempts. self.assertEqual(mock_sleep.await_count, 2) - # No final alert on success. - self.cog._alert_mods_python_news_load_failure.assert_not_awaited() - # Task should start after successful load. self.mock_fetch_new_media_start.assert_called_once() @@ -65,10 +62,9 @@ async def test_cog_load_retries_then_succeeds(self): # No posts should happen because no missing lists. self.bot.api_client.post.assert_not_awaited() - async def test_retries_max_times_fails_and_alerts(self): - """`cog_load` should alert and re-raise when all retry attempts fail.""" + async def test_retries_max_times_fails_and_reraises(self): + """`cog_load` should re-raise when all retry attempts fail.""" self.bot.api_client.get.side_effect = OSError("Simulated site/API outage during cog_load") - self.cog._alert_mods_python_news_load_failure = AsyncMock() with ( patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, @@ -85,13 +81,6 @@ async def test_retries_max_times_fails_and_alerts(self): # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) - # Alert should be sent once at the end. - self.cog._alert_mods_python_news_load_failure.assert_awaited_once() - - error, attempts = self.cog._alert_mods_python_news_load_failure.await_args.args - self.assertIsInstance(error, OSError) - self.assertEqual(attempts, python_news_module.MAX_ATTEMPTS) - # Task should never start if load fails. self.mock_fetch_new_media_start.assert_not_called() @@ -118,7 +107,6 @@ async def test_cog_load_does_not_retry_non_retryable_error(self): """`cog_load` should not retry when the error is non-retryable.""" # 404 should be considered non-retryable by your predicate. self.bot.api_client.get.side_effect = ResponseCodeError(MagicMock(status=404)) - self.cog._alert_mods_python_news_load_failure = AsyncMock() with ( patch("bot.exts.info.python_news.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, @@ -128,5 +116,4 @@ async def test_cog_load_does_not_retry_non_retryable_error(self): self.assertEqual(self.bot.api_client.get.await_count, 1) self.assertEqual(mock_sleep.await_count, 0) - self.cog._alert_mods_python_news_load_failure.assert_not_awaited() self.mock_fetch_new_media_start.assert_not_called() From 1afa433908d657c73f0b9202421f1d55c70338d8 Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 16:52:14 +0100 Subject: [PATCH 072/115] test: add superstarify tests (#14) Add test cases for retrying cog loads and skeleton for new functions --- .../moderation/infraction/superstarify.py | 11 ++- .../infraction/test_superstarify_cog.py | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/bot/exts/moderation/infraction/test_superstarify_cog.py diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 006334755d..57b5733a39 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -10,6 +10,7 @@ from bot import constants from bot.bot import Bot +from bot.constants import URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -18,6 +19,7 @@ from bot.utils import time from bot.utils.messages import format_user +MAX_RETRY_ATTEMPTS = URLs.connect_max_retries log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -238,7 +240,14 @@ async def cog_check(self, ctx: Context) -> bool: """Only allow moderators to invoke the commands in this cog.""" return await has_any_role(*constants.MODERATION_ROLES).predicate(ctx) - + async def _fetch_with_retries(self, + retries: int = MAX_RETRY_ATTEMPTS, + params: dict[str, str] | None = None) -> list[dict]: + return None + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: + pass + async def _check_error_is_retriable(self, error: Exception) -> bool: + return False async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py new file mode 100644 index 0000000000..847ce43ba4 --- /dev/null +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -0,0 +1,82 @@ +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from bot.exts.moderation.infraction.superstarify import Superstarify +from tests.helpers import MockBot + + +class TestSuperstarify(unittest.IsolatedAsyncioTestCase): + + async def asyncSetUp(self): + self.bot = MockBot() + + self.cog = Superstarify(self.bot) + + self.bot.api_client = MagicMock() + self.bot.api_client.get = AsyncMock() + + self.cog._alert_mods_if_loading_failed = AsyncMock() + self.cog._check_error_is_retriable = MagicMock(return_value=True) + + async def test_fetch_from_api_success(self): + """API succeeds on first attempt.""" + expected = [{"id": 1}] + self.bot.api_client.get.return_value = expected + + result = await self.cog._fetch_with_retries( + params={"user__id": "123"} + ) + self.assertEqual(result, expected) + + self.bot.api_client.get.assert_awaited_once_with( + "bot/infractions", + params={"user__id": "123"}, + ) + self.cog._alert_mods_if_loading_failed.assert_not_called() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_fetch_retries_then_succeeds(self, _): + self.bot.api_client.get.side_effect = [ + OSError("temporary failure"), + [{"id": 42}], + ] + + result = await self.cog._fetch_with_retries( + params={"user__id": "123"} + ) + + self.assertEqual(result, [{"id": 42}]) + self.assertEqual(self.bot.api_client.get.await_count, 2) + + self.cog._alert_mods_if_loading_failed.assert_not_called() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_fetch_fails_after_max_retries(self, _): + error = OSError("API down") + + self.bot.api_client.get.side_effect = error + + with self.assertRaises(OSError): + await self.cog._fetch_with_retries( + retries=3, + params={"user__id": "123"}, + ) + + self.assertEqual(self.bot.api_client.get.await_count, 3) + + self.cog._alert_mods_if_loading_failed.assert_awaited_once_with(error) + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_non_retriable_error_stops_immediately(self, _): + error = ValueError("bad request") + + self.bot.api_client.get.side_effect = error + self.cog._check_error_is_retriable.return_value = False + + with self.assertRaises(ValueError): + await self.cog._fetch_with_retries() + + # only one attempt + self.bot.api_client.get.assert_awaited_once() + + self.cog._alert_mods_if_loading_failed.assert_awaited_once() From cc59caf5933b2a1abaafd390fe4fb7af5d4bcbb9 Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 17:05:54 +0100 Subject: [PATCH 073/115] feat: add logic to functions (#14) Implement skeleton functions with code for retrying fetch, alerting mods, and checking if retryable --- .../moderation/infraction/superstarify.py | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 57b5733a39..8139eaa37d 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -1,16 +1,19 @@ +import asyncio import json import random import textwrap from pathlib import Path +import discord from discord import Embed, Member from discord.ext.commands import Cog, Context, command, has_any_role from discord.utils import escape_markdown +from pydis_core.site_api import ResponseCodeError from pydis_core.utils.members import get_or_fetch_member from bot import constants from bot.bot import Bot -from bot.constants import URLs +from bot.constants import Icons, URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -18,8 +21,10 @@ from bot.log import get_logger from bot.utils import time from bot.utils.messages import format_user +from bot.utils.modlog import send_log_message MAX_RETRY_ATTEMPTS = URLs.connect_max_retries +BACKOFF_INITIAL_DELAY = 5 # seconds log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -45,9 +50,7 @@ async def on_member_update(self, before: Member, after: Member) -> None: f"{after.display_name}. Checking if the user is in superstar-prison..." ) - active_superstarifies = await self.bot.api_client.get( - "bot/infractions", - params={ + active_superstarifies = await self._fetch_with_retries(params={ "active": "true", "type": "superstar", "user__id": str(before.id) @@ -86,9 +89,7 @@ async def on_member_update(self, before: Member, after: Member) -> None: @Cog.listener() async def on_member_join(self, member: Member) -> None: """Reapply active superstar infractions for returning members.""" - active_superstarifies = await self.bot.api_client.get( - "bot/infractions", - params={ + active_superstarifies = await self._fetch_with_retries(params={ "active": "true", "type": "superstar", "user__id": member.id @@ -243,11 +244,43 @@ async def cog_check(self, ctx: Context) -> bool: async def _fetch_with_retries(self, retries: int = MAX_RETRY_ATTEMPTS, params: dict[str, str] | None = None) -> list[dict]: + """Fetch infractions from the API with retries and exponential backoff.""" + for attempt in range(retries): + try: + return await self.bot.api_client.get("bot/infractions", params=params) + except Exception as e: + if attempt == retries - 1 or not self._check_error_is_retriable(e): + await self._alert_mods_if_loading_failed(e) + raise + await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) return None + async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - pass + """Alert moderators that loading the superstarify cog failed after retries.""" + message = textwrap.dedent( + f""" + An error occurred while loading the Superstarify Cog, and it failed to initialize properly. + + Error details: + {error} + """ + ) + + await send_log_message( + self.bot, + title="Error: Failed to initialize the Superstarify Cog", + text=message, + ping_everyone=True, + icon_url=Icons.token_removed, + colour=discord.Color.red() + ) async def _check_error_is_retriable(self, error: Exception) -> bool: - return False + """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" + if isinstance(error, ResponseCodeError): + return error.status in (408, 429) or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) + async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) From 62d115a0b28ddeb064cafb9bb605050e8367eb24 Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Fri, 27 Feb 2026 17:20:30 +0100 Subject: [PATCH 074/115] test: more unit tests for superstarify (#14) Add unit test for on_member_update and unit test to check _alert_mods_if_loading_failed is being called --- .../infraction/test_superstarify_cog.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py index 847ce43ba4..4e3004ce6b 100644 --- a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -80,3 +80,33 @@ async def test_non_retriable_error_stops_immediately(self, _): self.bot.api_client.get.assert_awaited_once() self.cog._alert_mods_if_loading_failed.assert_awaited_once() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_member_update_recovers_from_api_failure(self, _): + before = MagicMock(display_name="Old", id=123) + after = MagicMock(display_name="New", id=123) + after.edit = AsyncMock() + + self.bot.api_client.get.side_effect = [ + OSError(), + [{"id": 42}], + ] + + self.cog.get_nick = MagicMock(return_value="Taylor Swift") + + with patch( + "bot.exts.moderation.infraction._utils.notify_infraction", + new=AsyncMock(return_value=True), + ): + await self.cog.on_member_update(before, after) + + after.edit.assert_awaited_once() + + @patch("asyncio.sleep", new_callable=AsyncMock) + async def test_alert_triggered_after_total_failure(self, _): + self.bot.api_client.get.side_effect = OSError("down") + + with self.assertRaises(OSError): + await self.cog._fetch_with_retries(retries=3) + + self.cog._alert_mods_if_loading_failed.assert_awaited_once() From 1e733d54674d56199573892433c36e67e4f765d8 Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Sun, 1 Mar 2026 14:53:15 +0100 Subject: [PATCH 075/115] refactor: remove _alert_mods_if_loading_failed() (#14) Remove redundant code and corresponding parts of unit tests. --- .../moderation/infraction/superstarify.py | 24 +------------------ .../infraction/test_superstarify_cog.py | 7 ------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 8139eaa37d..180a49d304 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -4,7 +4,6 @@ import textwrap from pathlib import Path -import discord from discord import Embed, Member from discord.ext.commands import Cog, Context, command, has_any_role from discord.utils import escape_markdown @@ -13,7 +12,7 @@ from bot import constants from bot.bot import Bot -from bot.constants import Icons, URLs +from bot.constants import URLs from bot.converters import Duration, DurationOrExpiry from bot.decorators import ensure_future_timestamp from bot.exts.moderation.infraction import _utils @@ -21,7 +20,6 @@ from bot.log import get_logger from bot.utils import time from bot.utils.messages import format_user -from bot.utils.modlog import send_log_message MAX_RETRY_ATTEMPTS = URLs.connect_max_retries BACKOFF_INITIAL_DELAY = 5 # seconds @@ -250,30 +248,10 @@ async def _fetch_with_retries(self, return await self.bot.api_client.get("bot/infractions", params=params) except Exception as e: if attempt == retries - 1 or not self._check_error_is_retriable(e): - await self._alert_mods_if_loading_failed(e) raise await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) return None - async def _alert_mods_if_loading_failed(self, error: Exception) -> None: - """Alert moderators that loading the superstarify cog failed after retries.""" - message = textwrap.dedent( - f""" - An error occurred while loading the Superstarify Cog, and it failed to initialize properly. - - Error details: - {error} - """ - ) - - await send_log_message( - self.bot, - title="Error: Failed to initialize the Superstarify Cog", - text=message, - ping_everyone=True, - icon_url=Icons.token_removed, - colour=discord.Color.red() - ) async def _check_error_is_retriable(self, error: Exception) -> bool: """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" if isinstance(error, ResponseCodeError): diff --git a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py index 4e3004ce6b..54473c7064 100644 --- a/tests/bot/exts/moderation/infraction/test_superstarify_cog.py +++ b/tests/bot/exts/moderation/infraction/test_superstarify_cog.py @@ -15,7 +15,6 @@ async def asyncSetUp(self): self.bot.api_client = MagicMock() self.bot.api_client.get = AsyncMock() - self.cog._alert_mods_if_loading_failed = AsyncMock() self.cog._check_error_is_retriable = MagicMock(return_value=True) async def test_fetch_from_api_success(self): @@ -32,7 +31,6 @@ async def test_fetch_from_api_success(self): "bot/infractions", params={"user__id": "123"}, ) - self.cog._alert_mods_if_loading_failed.assert_not_called() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_fetch_retries_then_succeeds(self, _): @@ -48,7 +46,6 @@ async def test_fetch_retries_then_succeeds(self, _): self.assertEqual(result, [{"id": 42}]) self.assertEqual(self.bot.api_client.get.await_count, 2) - self.cog._alert_mods_if_loading_failed.assert_not_called() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_fetch_fails_after_max_retries(self, _): @@ -64,7 +61,6 @@ async def test_fetch_fails_after_max_retries(self, _): self.assertEqual(self.bot.api_client.get.await_count, 3) - self.cog._alert_mods_if_loading_failed.assert_awaited_once_with(error) @patch("asyncio.sleep", new_callable=AsyncMock) async def test_non_retriable_error_stops_immediately(self, _): @@ -79,7 +75,6 @@ async def test_non_retriable_error_stops_immediately(self, _): # only one attempt self.bot.api_client.get.assert_awaited_once() - self.cog._alert_mods_if_loading_failed.assert_awaited_once() @patch("asyncio.sleep", new_callable=AsyncMock) async def test_member_update_recovers_from_api_failure(self, _): @@ -108,5 +103,3 @@ async def test_alert_triggered_after_total_failure(self, _): with self.assertRaises(OSError): await self.cog._fetch_with_retries(retries=3) - - self.cog._alert_mods_if_loading_failed.assert_awaited_once() From e8c43572ce20b3259d8de18149efc74eba073ca4 Mon Sep 17 00:00:00 2001 From: Carl Isaksson Date: Sun, 1 Mar 2026 17:10:09 +0100 Subject: [PATCH 076/115] docs: add estimated effort Carl (#7) --- report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report.md b/report.md index f0b22bbba0..3288dc256c 100644 --- a/report.md +++ b/report.md @@ -45,7 +45,7 @@ Estimated effort per team member, in hours: | Apeel | 8 | 3 | 3 | 6 | 3 | 4 | 1 | ~28 | | Josef | | | | | | | | | | Alexander | 8 | 2 | 1 | 5 | 2 | 5 | 2 | ~25 | -| Carl | | | | | | | | | +| Carl | 8 | 2 | 3 | 5 | 2 | 4 | 1 | ~25 | | Fabian | 8 | 3 | 4 | 5 | 3 | 3 | 1 | ~27 | | Total | | | | | | | | | From be836c37bd461991b40a7e07475ff3d5c3a5fbec Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Sun, 1 Mar 2026 17:15:54 +0100 Subject: [PATCH 077/115] docs: argue benefits in the context of SEMAT (closes #27) --- report.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/report.md b/report.md index 5ef41ea655..5e6643ec76 100644 --- a/report.md +++ b/report.md @@ -153,6 +153,28 @@ Adding retry logic for critical extensions introduces a resilience pattern into Overall, the update strengthens the architectural maturity of the system. It centralizes lifecycle control, improves separation of responsibilities between the core and extensions, and introduces structured error handling and resilience patterns, all while remaining consistent with the existing asynchronous event-driven and modular design principles. +## Benefits, drawbacks, and limitations (SEMAT kernel) +The primary opportunity addressed by this issue concerns operational reliability. +Previously, cogs that depended on external services would fail silently if those services were unavailable during startup. +Since moderators were not alerted to such failures, functionality could become inaccessible to users without explanation. +The identified opportunity was therefore to improve the robustness of cog initialization and introduce explicit alerting mechanisms so that moderators could take corrective action. +With the implemented changes, this opportunity has moved from identified to addressed, as the Software System has been updated to handle such failures explicitly. + +We identified unhandled exceptions in the affected cogs, introduced structured error handling, and implemented a retry-with-exponential-backoff pattern, significantly improving fault tolerance and resilience during startup. +Moderator alerting is handled centrally: a single consolidated message is sent containing all cogs or extensions that failed to load. +This ensures consistent error reporting while avoiding excessive notification noise. +However, the retry mechanism increases the complexity of the startup logic and prolongs initialization time, as the bot completes startup only after all retry attempts have concluded. +This introduces a clear tradeoff between startup latency and system reliability. + +From a Requirements perspective, we transformed the previously implicit requirement *cogs should load* into a set of explicit non-functional requirements. +In particular: *cogs should tolerate temporary external service outages*, *failures during cog loading must be observable*, and *startup must not fail silently*. +Additionally, *all startup failures must be reported to moderators*. +These refinements align strongly with reliability and observability as non-functional requirements and make system expectations clearer and verifiable. + +The primary stakeholders include moderators/maintainers and server users. With the implemented changes, moderators receive explicit failure notifications, enabling further action to resolve the issues. +Observability is further enhanced through Sentry logging, which records retry attempts and associated error details. +Consequently, users experience fewer unexplained missing features, improving transparency and overall service reliability. + ## Overall experience ### What are your main take-aways from this project? What did you learn? From d13351d5e51f8b8bc801a4f262cf516388819d9d Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Sun, 1 Mar 2026 17:25:29 +0100 Subject: [PATCH 078/115] docs: add total and individual effort spent (#7) --- report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index 348ea19d66..92300ff3fb 100644 --- a/report.md +++ b/report.md @@ -43,11 +43,11 @@ Estimated effort per team member, in hours: | Team member | Plenary discussions / Group meetings | Reading documentation | Configuration and setup | Analyzing code / output | Writing documentation | Writing code | Running code / tests | Total | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | Apeel | 8 | 3 | 3 | 6 | 3 | 4 | 1 | ~28 | -| Josef | | | | | | | | | +| Josef | 8 | 2 | 1 | 6 | 4 | 3| 2 | ~26 | | Alexander | 8 | 2 | 1 | 5 | 2 | 5 | 2 | ~25 | | Carl | 8 | 2 | 3 | 5 | 2 | 4 | 1 | ~25 | | Fabian | 8 | 3 | 4 | 5 | 3 | 3 | 1 | ~27 | -| Total | | | | | | | | | +| Total | 8 | 12 | 12 | 27 | 14 | 19 | 7 | 99 | ### Dependencies and setup tasks: From 262838ca83de4fca023e2d979764086fcab3c0a1 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Sun, 1 Mar 2026 17:43:20 +0100 Subject: [PATCH 079/115] docs: trace tests to requirements (Closes #26) --- report.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/report.md b/report.md index 92300ff3fb..0d5d5e061d 100644 --- a/report.md +++ b/report.md @@ -95,13 +95,30 @@ Identified cogs pertaining to this problem are: If a cog fails to initialize due to a retriable HTTP error or network-related exception, the system shall automatically retry the initialization a finite number of times before giving up. The retry attempts shall use exponential backoff to avoid rapid repeated failures. +**Tested by:** +- `tests/bot/exts/filtering/test_filtering_cog.py::` + - `test_cog_load_retries_then_succeeds` + - `test_retries_three_times_fails_and_alerts` +- `tests/bot/exts/utils/test_reminders.py::` + - `test_reminders_cog_load_retries_after_initial_exception` + - `test_reminders_cog_load_fails_after_max_retries` +- `tests/bot/exts/info/test_python_news.py::` + - `test_cog_load_retries_then_succeeds` + - `test_retries_max_times_fails_and_reraises` +- `tests/bot/exts/moderation/infraction/test_superstarify_cog.py::` + - `test_fetch_retries_then_succeeds` + - `test_fetch_fails_after_max_retries` + ### FR-3) Error logging and monitoring All initialization failures shall be logged through the existing logging infrastructure and reported to Sentry. +**Tested by simulating Exception and observing the Sentry output.** + ### FR-4) Moderator alert upon failure If a cog fails to initialize after exhausting all retry attempts, the system shall alert the moderators of the server by sending a message to the `mod-log` Discorrd channel indicating the affected cog and failure description. -Optional (point 3): trace tests to requirements. +**Tested by:** +- `tests/bot/exts/test_extensions.py` ## Code changes From 8631534b4f7ea401adf6351c2e75fff583500d47 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Sun, 1 Mar 2026 19:15:31 +0100 Subject: [PATCH 080/115] docs: decribe architecture and purpose (#24) --- figures/flowchart.png | Bin 0 -> 52880 bytes report.md | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 figures/flowchart.png diff --git a/figures/flowchart.png b/figures/flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..b90ff7e957f730a1e4d079bd11bff1dad6fc0d75 GIT binary patch literal 52880 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 literal 0 HcmV?d00001 diff --git a/report.md b/report.md index 0d5d5e061d..3b32d358d7 100644 --- a/report.md +++ b/report.md @@ -9,6 +9,18 @@ URL: [https://github.com/python-discord/bot](https://github.com/python-discord/b A discord bot designed specifically for use with the [Python discord](https://www.pythondiscord.com/) server. It is built with an extensible cog-based architecture, integrating numerous functionalities, such as moderation, community management, reminders, and many more. +## Architecture and Purpose +The project consists of a Discord bot that provides a wide range of features to support the Python Discord community, such as moderation, filtering, community management, reminders, and much more. The bot is designed to be modular and extensible, with a focus on maintainability. + +At its core, the bot operates on an asynchronous event-driven architecture, utilizing Python's `asyncio` library to handle concurrent operations efficiently. The bot's functionality is organized into "cogs", which are modular components that can be loaded, unloaded, and reloaded independently. This design allows for easy maintenance and scalability, as new features can be added without affecting existing functionality. The bot interacts with the Discord API to respond to user commands, manage server events, and integrate with external services. It also incorporates error handling and logging mechanisms using Sentry to ensure reliability and facilitate debugging. + +However, there is no simple way for Discord moderators to be notified if a cog fails to load during startup, which can lead to functionality being unavailable without any indication of the underlying issue. This is particularly problematic for cogs that depend on external services, as they may silently fail to initialize if those services are unavailable, and moderators would not be aware of the failure unless they have direct access to the Sentry logs. + +This issue is mainly related to a few functions in the main `bot.py` file, which is responsible for loading extensions and cogs during startup, as well as the setup functions in each extension. Figure 1 illustrates the flow of the bot's startup process, highlighting where cog initialization occurs and which exceptions are captured and reported. The diagram also indicates the points at which moderator alerting is integrated in our implementation. + +![flowchart.png](figures/flowchart.png) + + ## Onboarding experience ### Did you choose a new project or continue on the previous one? From 7a48446686ca7bbb1735f1c5662559ddcfb64ac5 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Sun, 1 Mar 2026 20:06:31 +0100 Subject: [PATCH 081/115] docs: write the Essence TEAM part (#7) --- report.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/report.md b/report.md index 3b32d358d7..941c6fc280 100644 --- a/report.md +++ b/report.md @@ -214,7 +214,10 @@ Technically, we gained more knowledge about cog-based bot architectures, asynchr ### How did you grow as a team, using the Essence standard to evaluate yourself? - +We believe we are still in the Collaborating state, however much closer to the Performing state than before. +Throughout the course, we have gradually improved our communication and responsiveness, which has resulted in more active code reviews, frequent discussions about current obstacles and problems, and more structured meeting plans. +The coordination also improved, as we experienced smoother task distribution and better fulfillment of commitments. +However, other courses and external responsibilities still affect our consistency, and the work is therefore fulfilled with varying levels of consistency. ### How would you put your work in context with best software engineering practice? From 7cc7bfac93f7e768dcd11e7c475e1a26f6da2fef Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Sun, 1 Mar 2026 20:18:32 +0100 Subject: [PATCH 082/115] doc: Add UML (#17) Add UML figure and description --- figures/Pybot_UML.png | Bin 0 -> 181301 bytes report.md | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 figures/Pybot_UML.png diff --git a/figures/Pybot_UML.png b/figures/Pybot_UML.png new file mode 100644 index 0000000000000000000000000000000000000000..12fbd8e0948a6d17486baa08f2e5e1a0dba579b3 GIT binary patch literal 181301 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
GpI$XnYKFz(N3 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∈3j>nsQyvI17uhHTNprjD}A(J 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 literal 0 HcmV?d00001 diff --git a/report.md b/report.md index 941c6fc280..b9da48a13d 100644 --- a/report.md +++ b/report.md @@ -163,6 +163,10 @@ uv run task test ## UML class diagram and its description +The UML class diagram shows the classes modified/added to resolve the given issue as well as how the classes are related to each other. In the UML there are notes adjacent to classes describing how they were augmented to resolve the issue. + +![Pybot_UML.png](figures/Pybot_UML.png) + ### Key changes/classes affected Optional (point 1): Architectural overview. From bd7389a33d2b194faec59e0dc6e4f856ecb99028 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 08:55:09 +0100 Subject: [PATCH 083/115] docs: added table of the content at the top #7 --- report.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/report.md b/report.md index 92300ff3fb..f96bbab593 100644 --- a/report.md +++ b/report.md @@ -1,5 +1,33 @@ # Report for assignment 4 +## Table of Contents +- [Project](#project) +- [Onboarding experience](#onboarding-experience) + - [Did you choose a new project or continue on the previous one?](#did-you-choose-a-new-project-or-continue-on-the-previous-one) + - [If you changed the project, how did your experience differ from before?](#if-you-changed-the-project-how-did-your-experience-differ-from-before) + - [Setting up the project](#setting-up-the-project) +- [Effort spent](#effort-spent) + - [Dependencies and setup tasks](#dependencies-and-setup-tasks) +- [Overview of issue(s) and work done](#overview-of-issues-and-work-done) +- [Requirements for the new feature or requirements affected by functionality being refactored](#requirements-for-the-new-feature-or-requirements-affected-by-functionality-being-refactored) + - [FR-1) Resilient Cog Initialization](#fr-1-resilient-cog-initialization) + - [FR-2) Retry Mechanism for External HTTP calls](#fr-2-retry-mechanism-for-external-http-calls) + - [FR-3) Error logging and monitoring](#fr-3-error-logging-and-monitoring) + - [FR-4) Moderator alert upon failure](#fr-4-moderator-alert-upon-failure) +- [Code changes](#code-changes) + - [Patch](#patch) +- [Test results](#test-results) + - [Output Logs](#output-logs) +- [UML class diagram and its description](#uml-class-diagram-and-its-description) + - [Key changes/classes affected](#key-changesclasses-affected) +- [Design patterns](#design-patterns) +- [Benefits, drawbacks, and limitations (SEMAT kernel)](#benefits-drawbacks-and-limitations-semat-kernel) +- [Overall experience](#overall-experience) + - [What are your main take-aways from this project? What did you learn?](#what-are-your-main-take-aways-from-this-project-what-did-you-learn) + - [How did you grow as a team, using the Essence standard to evaluate yourself?](#how-did-you-grow-as-a-team-using-the-essence-standard-to-evaluate-yourself) + - [How would you put your work in context with best software engineering practice?](#how-would-you-put-your-work-in-context-with-best-software-engineering-practice) + - [Is there something special you want to mention here?](#is-there-something-special-you-want-to-mention-here) + ## Project Name: Python Utility Bot From dc127bd8ef5e387d3190982f28783ce7b9c2a20a Mon Sep 17 00:00:00 2001 From: Fabian Williams Date: Mon, 2 Mar 2026 08:59:21 +0100 Subject: [PATCH 084/115] doc: replace UML bit map with svg (#30) Replace png of UML with an svg --- figures/Pybot_UML.png | Bin 181301 -> 0 bytes figures/Pybot_UML.svg | 3 +++ report.md | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 figures/Pybot_UML.png create mode 100644 figures/Pybot_UML.svg diff --git a/figures/Pybot_UML.png b/figures/Pybot_UML.png deleted file mode 100644 index 12fbd8e0948a6d17486baa08f2e5e1a0dba579b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181301 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
GpI$XnYKFz(N3 zi95;kV>m21v6s>C3hWYT$8CG1&DtcA*Po+der!efJBT4--ZqHQesD%4kid`MFQ#hh zN14x{>T2uOy)rE0H3bKWR)m@9^qj(S2i3BNVfu?gb``lGzFUH~EtCer7FlE)d3Two zlvodC_>~Ur{ByKtEh63q|LX4Q$cx^eDk)-O${<0Ofs1ErbiYG@rAt1P;?b9v(9dXy zQx;ii|7r8E&%%uu5q?K%8I&qy;)t3iCd$X{ zs=jvb0nA0G<9~{kTc;p{XeaQ|1wmU1Gh27&23mE5mN zZUb)dDX38y0F&Bmw}OB!Jpdmuo12V=2;cHgUtaZ}zT985V}3pzB-XwN)gL4v+Q))z%b57A2PdKpAwOU@va&?|)%4Gz2j!yWc;^!Tc&2`ko~<7_|ee z9T^UMsKht!6@WMK0G8lL#jY!)1H&tr{{-lHECy3J*ETlHf*%*iignyTd?j_;Jimw) zJOk5A8JwkdE1kG;Cf*l?$YLGzIMCH@@o)41r}Is=BBy{a=#(PH5e8uJVL)#(hXI7W z2f(k+t{I4Mz0z)jx6eEHG&A*&LlvBGVn}V8wehuf2+i3vge(J7t{V?p_x8Mi_q5$jStXn zBrTNj=y-3y9F2%NZ_;J>_1qSvEb#QX8;wJrBS8SnW$^=C1!U*^fhCe*xPf@goZ!Q_ z!0`izRRbtrDR{NJ(g#bTunt-?fEeBs|Eqtym;+edHZl`zB&WE5AvIs;R{_;ut!X#wD=2TH@8O`ACOK4mXvn$ZOf;VcNQQ!GowM+xY$j90(415qB> zI$%hHhfJDJ8vVI+Jo}k~lKXmAYE=P?W{;+UeJOB8 z9T`tg-kgp-QQQaM4!r~&5gGzLwcUC@0T$(4wK2+Ml|i<|laH9_?t#!tkw~smyDzd( zyQhczqc2#>t@t7U>LKKt;irx29Z_DP&K4^k$rpK7%F;4|+z_lubY z)WEy{NSOp?HfPOTC`G&n3&;c!t)}n1A&m5pnMVj|vK-=Z4|B&4Qey}HwVj;-6NRt- zNZq786v(<)R1idefE&D-n@ikxCC>rrL~ViTlclzlIrG$eD!sYt;5J6vl)Q>-Rj~B3 zDJA50tHpE|{GXe%S90nltcg29!FdARr|LnRN5=<^RgV9+lYMBpzyI!`n|ofw(CW3V z_=aVg;b6eLL?K!gpXGzS$hy!MXFow{=R$_*AF5Y*1f`O_U-42kWo;2luu=-W1aL#|GOffbP(zrDhtCuiCNA!wZIGIK&S}Z~*q4;OL&au~~3*cZG z!MaGXGaV3&+Y#XhsD1JNyspsxU}bJX(}}X`9B0^JZ%o5GFM>d&5AJLywa1J?$2X)W zeE|7o1U=7gp})0gnlJIzL@6G+Chf#XV#fuU^h0 zT$LA-3R2^`+-jqIw@j7|o^>wUDsnOG+=+XqM0?rK{ekvCs(T(g@4_g3q$kj9hZ_4$b8ctb4BJMO}PZas#_9=@ef|pkk^YNE;pbHg=>_m9iifOh5}B( zB#14M9u?sXtb@vxPGo-dAlMqs5!-Hg^)5puitJ_2YAKZ@^eh0APf%Snji$Uy1AbKF#U0L9gy5IO{^60wfmx%vo+I4BpUt=OC4KwPaadeF}_i2fM7Wg^zxg-j~p z!fjrPzap444WvSUg3h{COu)7U!QidC^Y!DQ%Gh{N?iXwx;gU3)?H+NptA3EvX=24= z`#iowIN~=;TRPh*^dMW_Zu?CU6ipl^WJ$=we)QyC66jh*_RhDQ(1CebIE9sExED2?8q$gc1qDUM)YLsaS_A^Wru~`^fpMljNq?7u6Sa$itPq0DCGJ?qTe3L_8GCcYG&h zddTu4MDVUeqNJ9fydk?g;eK?4AfXj%HeRf3UaeD*3nVppfHE2X-|nsL2y}^g7SSIL1D|!jF7^@8%+$u?n^*iV0DECqZ9p5HH`HJgGvD)HTpTDJL+jy zovASus3XZ)TnKw~YCnzq*`Tf_)!^%R(d)(M^m;Z%G~B{W0oJjDSXyTNQNJ&XVPNi6 zu0r7_Rpi21u3By9GJXqY(@K@}GX;{)f{#9t$yIVOE=HyJ8qGTpumP8P?{Sryw6~{n zcXP<@TKPC1;NYa14cvXCeL!%0yDgFt@rY?DvTjUS46dZgfgYps;5I=zS z*n`3tVmqP)tEZEJD*J{%&4`Rah;95i%R1*SrE zUnZo|ZGTY!2N9~6(KL*8LE=r=ysNMR&nH{D(bMi_r9wqaYq5F00*PH%M`6F3WP7}! zV)<#%Yg~RPM;UgC|0Zd+`uyd40XA1NHJhix--9Avt1d7*UM&KhvRfbB?Py5=+gM@P z_1aU7-7-@NZ?1m-uyRBenJ3-v=^ZA4QYc6lM8Ol-4D-mD6+q3B(+n>E4g33;AK$J`1aePD-P$KgsGYy9dbX3BsO zU%Ik{G7$Q&EiWD^hvC>)$~biHzcxPybmJs$;*AqlNSQ{Sdj!5!OTb@l4g{cH8MMYw zw|bhs^iy7c-Vq0~Jgs}miqC=MPoj0RkDRL)MlK~|*Aa}E?IH|Xt&3k4+wZBN(kQ&H zBy7Csrg79}?`B>=Cc-$I+(_&opHhCowU@FefuJ*oX=Dsjmx`3>n<{_c!=eBFLNWXi zn>Hoii`0uDRgK9hZ7-gJ(Ik}qS_Ln)`_mQM|%Hd0)uVRz5O5D5g(4g z%bqxg8ae_<67R|}6<5>{C^Z67--90p3d2AqD1%9)lc}B94Y_C#3|w$gRAoB~dobp& zc2+2w>w})PHu|UF32o+ArUjiA5n5SFI2~})m^!%(6n8Sv2G`JJSJ=Iea^Dzv!dk0} z|0!u0oB-UOLr#D=s-q&!NPdQx=n_ea2$K;+5ed8%j6I`eEmusU<+bdAT!i7mg>iSd zHG;>5K^aa(vfu|>Wf!M~&;@hs0Ufg@(AV4=hw*-adQEZTMhjMp%7q4d(HtGGDFB$2 zxJ31R@)z4t2a~vzpAaM(NrdvUBs?*xP!yU*BXA;M#`&76()dCQrC2l7VJ4eVy(Z}X z$~6n5SFzR_6Qa+I8Bxe%d_u-BG0Q)(IRl>552g4%k$QP;ty|g|LL>ZY8R0?Vw{9F( zm}(g0!A1~6Nz6R~T+~c7#I2^~R+IKXvC(WH9d-Uu+g$}(?0td?A~pz~HwtbMO05DTPb`RN^;7a{4`mwI#V)Cxpa zh0HeLs7M$@tob0*k5oG9F)_PpG#))(GCgZYhA;|Z4iv#RrkBTf`1n2sRC>V(>iY#Z zp`6hmFK-fvQT7XrCefriRw2s^p$c;VpqSIa>v;njXLO4{g=kjCD@l)LN#Og?lZf1f zCgs5|>kaSOsZ}OtzUMKi$J9MAs;e`v(p%;4KL{P;o!$8%(f#LT^rJ(V_~yAi`Iq?u zpxiRM6+h9PUd3edr!qt=#EP_7V)F4LO9u7L8od0+5_l zb94Y2)ls8@RCb6U36f90WKZy`V~(G{fHt5jgk03}ALY$~ec`0|T1^JD-HvA6$Z_k*WO@?7=lQadnra zE~QqnW#gy73!+xQ{Trw)Qz}>$F}C-0Tv?`JkyNYnHM>%9(`ELe_$dPrz%$7lE9(|4 z$;J?fMdaO34|idP0DZm(V6u4}epU{W z@mh69M-~TQe_ct8K|wUZm4qwb2O>4n5Fm^p`;b^|G6fpG zoENVUfkX5$_1hX>D>mWrv|3{yMz;(5S@iC)9lp-TiuPa9<&hDW-;bynF9J|MmBkDl z)ZxYU)ldE|7SEkbESAeKOEr(^*3Xz3*RU=XfGLO}Lrumv$Wa3>_n$z;-!3{NbJ(k=Yn*-L3!a+hF6QuXBt%wi z;9*e*zz$~^u9WB0;2<+W$xsz2)6JbB0r$7>zZyth8LC2So}<(`u?ez5zpMIF*lcHf zqcLZl^amMf41afjeA;9rr&v+G@Aa>$qMD?X>d1zmD8ho7d|w?1>fKpY(+U}H<(}9o z?Iows%17IBAG`NEwue+Ayf^`!#D!@Hv08Is^;zsSPA)SU+P7k08>(d-DYUiu*x!am zE{<}7{DJK#x#`o;knYBFzspr-eBlI+yaI+(G32MFg}qA9t#fKq*yrq$(%6ev68C_~ z0kzJESKy3~P_HZ$<_P+wLS3Jrf|m`SC-z8%%0F$Y#ZYRr`}lylq<;-m=0a#*fvG5C z(gZxVs@?ROwU-wc8yqrD14l`XCcY8>+t2^^M`$d9A_?2m!K+2!_R`7|e{+?HL!)%1 zsC>0t_-y7EH{9Fka%u<|S(;M%_{_R4Byzp(fxn))A?B^Fk}xOA$-YF?LC0QI;Wp1^P~`Sue8B?cVF_V5 z_g4r0pZ;$DF-hvDJ47I%Ub>ixTps0nW`e63a8Rsg?lpb!r~6mhbN(ysS#$^D*qw}? z0EF~xV_oty2o{9%g+aLsjbUR>2C`V2PJBMF?4{;V7s`ub`BTxW53WOOzc#N?e-@!r zaV5JHfz8@QQ&~!iniF*9!k$~uJoZ#3tx#^8Az1+65hM`(gFB@VD4jv&QwoHW!iotc zS)m;X5b6;Z01?9BL4e5<3WLHon(| zFdg26jNmBZt#7P$#-Yu7jboPIem(3cmCcNmfks~N-Jnr8wCM-Oh}ZH567=L(vpBMz zMpdGvQTfDw7R!bG`>BnYsN;z3Mp3-QqB2f@>Ay1Tl8aI#dsL^1)IwM;nNkhR`sYRd zbubyegXf^fAU6?=ZZ#MFbPT}atr$$D3VXCkbwIc(_gs+T3nx$*y$!wXe2cby zxfJx0(AP?4M`FU!ZJ=L9cz+o}U4${YZQAS^EgwfJmqfe0S-ZVFu6*fCf*8QKDv7j$ z1>S9Cve9Q#2VX4=inqZ9q6xkvSe*r8TE4KSa8oioOt;`zpHF)-bD0)^cDGAWs^Xt1 z7YhWHBk2LvACkWNNt;I@nvl`JLJ;5z&nY?F8h>--iH~O_P%$fyP;-C$nArhp{N$8L z*N_=>>7=etDL0TLC7}R_wNI{v?|47!41406hehs`|VYRVj z0@dbl%1u8e*sb)bT?ff7uK1hlRG_RGwm5^7q{+xXqmZ>vr$PZRA`0UvT#W2{{H<&< z0mNj3WZ8?9WE|;`wzjqiD6P_adks4FH@GO$kq8@+Qg0t=feZ#VcrsWeS?p^2!+jPo zcq8r4fiWR3Sv}_&Vqw$O>Nq#9((j6@xXQZc z&{s|FwS`Xr;%Q%yBII5Fl`ks(a6hf_TE?!4}|BaJOv# zBTYZ54M<1q*^jgFVA9B@LtE>(m`a-9FEb>h z?|Bk0TT^ST`r$e0s*T;S409=e(C#v1&{T`Ox%LEs!$oK~l&|d8;qHlV4nI~^6%^58 zuKc5RrDe&%T7!4P;0(c%>p*jOqQ4!JP5$xA#C?VgQfc(oqj#TY|F&bpptno`r z5>pC+z(xyKBs+(WVN}LgY6ohjcyUain+{RXZFX^nP^%(Ravo_C!e3N3a*`MdT^g{< zdmKd|9aBog41TT(K-7I=$oBg=sQGHIQ;ABa6m5P}3%Y830)jC?K`OSuHGreP-oRKV zoq~)@&_aXx=z{T^fi+_aBnZ7oCE6}>?p$npGrY*5$mLA#@E5e{E^T3E^Ic5B>SVS0 zEg)kAL*}#Lx0vn?_&x79hIUv;T^=m_?VtvX9C`9|z7(?Zd+rjuZo$PhXd&f8VVfq1 z{J`~ePoblI1;o+bB0XV_PDeh7Jn`*=rJcBWnA=~ep%;*VyX!P`%n^}fck#XB3{na- zM4011$B!}%DuR6wxk(^6`%<1#Z1 z@?pmzbVVL@qF{U`vZftMrjM1eqRk-&*K1c2A-3aarQ1lss+ce|n1U{Nlt|Q!K{Znn zcyvktkUa0^TH**+43q%LP!Lbr@$MyF5Bc%If(V>BCS=m=SbhJ<=4ln&7F79?wU}N% zssmUVW=o(nvR6F`RC$*D@oXdYgz;&-O8>1FNhl{H615pTlAvq>P$xVu^|b5Z)hH7Z zbRFa`FjLLa>fk|061hODpPcxVj6$dN16xn5siLwFe_RKk54Q4x?pH>r`yQ<*uc^x3 zzi`evsN*&LKR+Fc5*i+y?rzu>^AKF6Uk0r~G^cu(P?g>*TD zmB6FWPW$@*RMPjjU~}#2mDo-OMIp8v+&O@8UaFj?9Q8ybC;)5`r-eF2Q^OZpvAm(H61SO8iKb~T$)EUYsh$&D>!A>Bp+y>?= z_~x8)kQDpx7=s1g5q&^8J^+l(^|4=*_de+Z8CnlssrAkGSYQQ|h?qnUf&@#Mf^r$3 z9VCxiYyDp7j?_ths)X|F-#+1lhdAO0@-a%uh1@m2+KyTy+NKmS#nnFtjsm~)9m3Bd ziGQIMBhvBnyLKvoT|K1^_U(~q3Vovr@>n1ieL(3z57MkF3~CF>NwQu-MocSYa5=gN zKHT{xKSl}RltVDn_M1Cwe^Bp^z?%9b$gBXCgQtAHWG$Yj=YwgE3ZI7CmqkvTYV8VY za}3B<6F=9>Ae&rn#%8egj_8oE6=0Zx>(9Q2M$9(%zoAy;ylIOFj^vBJ+UcuFDq&7M|v6TW& z+vroDs~+xe;@SdkC2@;nr#@>k586V<{hckx;@|ot`3_}6b`9zc4;^TfSf|O@v*W{6 zv@AjpHXhNKb|Kl9%fL@ULGrLS4z%?+S|2fPFU~RE+}%dgG9jI3yjqbl$<+^T8v0Qc zqCJiSlr*$B;ooSF&ZiWvOBJ zHwMqS$<+mY@c19@a%P=v2VKWo_tuJJ_bv!xE3tqyENr6V?>z(MhbAfp4{k==ETptk3Nx|o^CfR055p5eph8uLl$VaNFvj;9g~W4apx{G zOGn+KGHd0s%Xe_~iP*O)bGwWzsjxp^TFqZGN&ii#aTZG1Av|a5SO2Bf_qPdvckLv7 znmxbedC^Fk&9ZI?>`8~;qJ>&mK%U)rZ$PN8+}A%s7+P@t{|(p7GNE9gl)$kuPN` zw+7dOrWhb{=lKhHGM?KX@eY+kXqG@$Je;(zwI-|(n=ol;F3zu!`fssV@VMMNcJ?7Z zwTs_To6ftZv-g;>mpe2QFUpD(12gSxfu!vjgK13NfeO7aW&zN7PZeQ2gbk=&Z>;1* zxZ@Xl)T;KE{4|G;InrWc?ERx%UCJ(a zw=!?@_@A}Ez|3=Ywr+!5V`(pXG z+dQ8I0-T;Y?6iNq=;u5)`r5aJC6!4ByKQs+zACj;l9^?`R08g4co$6!mCb1|D+8fe zH#DZX#$pOB4A{%U$&G)s%XVaDcWu2c{m$;sF@T7lXFyopD((0j%*J6+fuwFO zr`&7IcQU0#9(%8H!% z_9shE>|5*rrsgVK4lG+&dY_f9qntNj1|-YxF9KKqEoa@IlTB(WOmBt9oA#Pa z?OS1+-w?3<%{5$2v}P_>^IjP59vL!Q{`sg1gXzegvhzJ(5UqPVquk_BpKtul2v3?U zz2S2o8LiU=bVKv*bNHWcP7FB!6q*sW$4Dzl&WY42w&Rix$^2b`h6FakGdmq~8&I$d zH*Ypn=7UtOlY10vl)bIc=-UHgeN$R^JXS8V%=FD-@|TLcQQ%w}w6YcelO`x_$_)~J zi_GZy$4Ku$x3Lbjk~f(Y*ZmR-KqT-~14rK6+QVLHi7`8N*os$ZjorhcICcz|K#1(4 zC#mwSH%HCUB=js*`%|;ub6MVgXa_i^@(hk&OUI3q#Up;h5KXe1qbycq&g&yL!-Mqj3vB} zXJ)dC6_9D4AE;d^4>)hOfS$u141OM;gm*Hb zb((ZOu%4)ls0&q!u0zNA3aoOc?-RVOUsQq*4~K6W=02(OjPQ)dPDp3cer&R|-#v7J zi~S2SE?tiwW5#kWzo&ns?vw0qZUT- zC}UVgrVL*3L)!%!b4!Jh34^E9-Xc(Af>6hPSv8Q^XKy)35OHbZe_6ZqQNknTgJU0$ z{aZc=b~>j*a1GnS9f+j6tLWYw2l+rg2?S=od%0R@v(yU9qwV&JjuQSA#13M}+{FIr z0}cmWffzS%vDSm8OZiwknRJ{by}CMDngGr`IvzTj@h(C6+lPLs(`cJ1`^{N#xJzJ2 zza(UvTKFmr$g!@F^PgYxt;!im+TlOGU$E|E_tv;+k;3)ft-2^9!+-kWwW@i6yt$@S z{9Z0^Vmy0x4=L++g6`(m^PINNj|H-=IqbBP83_^vnTTTxFUZEUP|_ys0&1V`5l*?> z*#ouZ?#I8LzMI0$05Ik=f+YamQ|HK+uMWTZLfBH#)ckw5E_CtdXy*miaBQ=SKB+Bk z6gUIY@x5qmC~qhY0~ip%P)J*KJqZ{C-$~X*7pR<;6Ss8rD``TIkJy@@?*0xGXl?*K zwxQ$zspQorSyV`Lrnix#z-K_Yb(1wK#E8$~BT#GJF&O98M0y;Gs<|-uY3}W4s}9yh zX}2Z1^=G1ZpNA2xi)N`8psv|Jr3D4asx!_v`<~c`wqVV>KnEDZxbIqbR)#7p`05qA zPEM)*Ku1<$yb=4P!jgb$zTK&`WDf?!lw8z;CtOW$v{ofkGlY_I>4fsyLsOi_|J9qrk&_QOEQXQB<5(5QFmZo3iv` zaP$iz#MT*xjXh9LcFJ4|;(zLX@AHEdxSs+vJ=T^l^6)r(W<;FY^093t#^15LZ_0LQ zIeZ1dA6Tg0Rj=I~%AWVd3d+(ICVXB3_B2>$E{cat+aroOx`W*^4Y*u*L8~XGHUJrR zZu+~m)0CAuRPv`KWf%Eckh*Wa=ye>QX`OG?_u7}r6+Gl>tmip`J53J-lY!-G0;w-e zFI$zZXSZ((8fL!(q4xSF$si#EwYe&6IXoW9jJNyM+%3hRX|3Y+C2x@-zqpc%;7c+# zXPt@sZ9I}=ZL{!mYF$W5_GV*SfN;1H)*=gVqG7WF87_a*;7sa%9d(^YRnaVJ7 zZ!)s28;oNoR@rw8Z$WreOgZmr?>v_Ej0tNflr&h#@=d#KM2dB>2W1*#I7bqmVYT+QEB>?x(+SE3sL$B^37rbIS&y&$A|Iujq=dxmRahRaa2cgr$!`N zQ__*eE|I%}uyX4I_O4{Pbi+l@iUgTH|K%{BR5chlj$ zM=;1Na-Kz4ZGL>m0A7r!8m)_t_#WO$z_VP0!4Vin+$pk8bsqROv5Lo@p7>AyS}(Q! zO^&Je`hg`0Se#!buk<@Rf0~ z&!gGK(H+JppGS#tT!<6@Q~$AS9nL2)h_w5wn#XU6rt2kIG~2EzGNU>SWu_9kM55o4 zt!dK1c5}e^kTd<7&j?R2Ef!zwaFX0a6Afc)qj(*iDf#|bc_n1Jqsk)7+?4m4infv3 zEK%#CdPb@Q<7?(&xB#Umcbu4Mou>WPa<9tT$Sz0??)6ds`{Wo2N6cr%{zlLT>L_UN z%ix8hKY&JlCe_w@zn6vuJNHK8s!l0%J~(o46LFTb+zmdS2PQL6R)_7GPlQHe6t+IU zbh5=FQFRxr!p1%>EJ>Cp1HK1;8<(5_V{hgXbPwP$`a1V*npwA$yZy@y);!Yg=6Ou3 z(b4PdAv!U>u+k1?D`}SL%1A?qvz0AL?fNTFxKaJrdu~a{m!^_vuiAh#(%btr3YAPQ z@S;L&ZHH!IG9PQ(kL!M@kdCWq>O@+Lp9#DV0!CvS>+@FmDB?HAo?(;V(aV30kf7%? z(f6Dz3EWF6)1%28pTRCL6b7RH$HdCgW9kbxTqBi+3=|SxB=MtY62hM|MZ+N zB{k!%rCLS3)`8Z`F(@hul4tTI&qzaFoakGwP%G4l5sJQAeu9hxJ2gu8n3hzGCMH+Z z(0rOmWc$Nv2{{JuS^YM`P}&~!C{~#3=i$KYkW?CapC~fQPeQlhzA$A;zR`-@6h;J) zex#n3!z$s6h;}Ca?Q}z38+T zdwOlX?nH7<7=}+X4WZ=VY{Wf$&pit*!^(*&!eAgVK6&Mb-RtJ_)p)5rZoyCI4||u3 zx1lWx+c&nMsWZE}dMWa{`MlH2IimmjmJ%EnhC^38=M9XBJ;INT5x5Ebu)u8vtpcH>s}1~Dn$`;<3>F}jU`qS8A`n$ zO?Z8d5V7Z3`yEb@jE)KTD=QBI4U8+d{ekw5PNyK7a_L;-3VTxknf|lHkI4xRg;-N zB1L?JL*i3JsQn}X`Uu6%gnT|gfANgkK&r9JiXBMp_gay*k+xL2s8ckJ>_vK2Qnn*s zQ(4ZBJA`afVy+YDqP5w*eGnrY3ZcZrJdI4RDPFkddDO}rb<=f^k9;tAoqjJ9_iEHT z?R+aQ%_DPG>Q`@QBw&s@yH%>O>5)EI25s)o1VXd#J{sG0KXWGO!Mz_z%+=w-)Ak?k9 zHs8Gn#MF_73@TVw2*`8!srBe{JZHr@Kv~7M7_Xm|d)oKCMuvo^>zyT|Ug7)p+mr6~ zw(l^#;)!=U9XfStS~>f3Qx`SA8drF}r27)@C*^WSj+rkj&(Wc&uVQG|ogghGV|j)Cc_b7OG9ZOTR*a%e_48_1r05v~B<#`ua4uWk zy;IX{b17MM4773XdV89vi#f;LJ+SBg6k?xb0dVP2Y%u&Kyb3~&g(mJkJ6Wxkfb4Ax zo$|47cGm_+HQSB*UzE_+g#|h{VnK%eM&vlZFhU4Z<359GUYg0_Q-*Q=zR`K3^2=4Q zLpf5p4c8sseIi!waFOtDk&UG=vvGPhWRYh1f0DGO;ixu^47fT<1BMp!CLq=oI!Wn#b){E3E>Mp<(I;Z*!LOp4tU2;kqz%@a#v0sjDR zfO@hMK`El{S>hxGcRoIXQ!4S$aUN!}DB^%K)~WdX^lEsPM+dzTcC*h#UGFaoW#pQI zf0SLnY={0&Ty71ajZ@~^?CPMl~Fi>@fhVyTn{fHLlZONHwz^53F5I27f**a z44lMPDEx|>kP<4=tp4vecW@x#IXu+}1E0>a{_hm$6r@NG5CG7+itv`zgaBm^;a@o& zCL2ZUQTWUT1F&7=!8&axO6D@>@x7wS%r91}`D8LgiU<8ir z|6KeOG?`|L47iyxqnjlM4+TiI;e|em;{W%^06gppa)B7ZU1>yT2gbu)VxXrKO|k|O zBSz(Bi&4ld58QI)%91rN@R(RUln*DQP7NT?s*>>Ej;RWnFEf!P7d5lJO+xUbFE2uwj;{m z$-_EnOB|yzlXz2nYP7)o)zLYIgx;`bo`DzxDL&@ECx(Q4Rc?dXBR(v39|A50UJnz- z*Imfx;IoNz_%4G_)HD3;jgkFkqxiJN)L(O{gt!#Phr!e~AGt2ut)T;~KU`nPT&I&a zSqbc3&{T04#HGZdhHwJ^Z3dN8?(+sPQE1_^Ox8tlGvV^H>3C1_VEJ(TF7TEmbCTGU zOG9hkfGd%eiJw$MZEBs%y$MmCtA|tTQt#3^Tk6l4RE#Ka;ky70jp-z@S4ozF==NGx zMrw--8OycAMKzMBi{rNXl=&1V4-b#f$e&ra%=hn8_|>|{HM>VfM(i`6klNbX?nt~m zam<66R=swvvTD41^VifFqdLBL;y2GrTOZsr{4y%BrobeA90!fd?|JzoN>d5G?D5St5MAh+;PkcfN<}-M_Ea%8d`>nO$=bPKm?u*$Fl1UqV(&) zDQ?fxEy`RC`3gQ?u{%6A+wnKS(<6jPh+)BVZPCxl_F0b$_fnr7MhJVqQjo;uV5c@E z_)7NMjLhpz2rA;&9I|#O$6>BSsrG_A1ow;82-P1hYoZe79d-QQJX(9HPnd$%s-;KB zUvTDrU>B2qa50uv*hnbaS<^qJefCL!DbKh?lI*&nfQ63uufcM#&!9myL+|&*x8gxQ zj4>I{aSpYn=p+A-1PYm%HzhbwK1Ni4LJoR{D=4v;Wab=F+dK({IUNaF{qMs*ut_MF zJ^xDvu{b~%?(zsKlBRDrcr zY%|#<3d9;yUHS)q_$+BkSPdcqvGIQ<8I{y)LBi}%MlRHh(M!Mf#D3AKhLP>ec2Xe9 z!LFoXOtu1z?MOSH#TOvbg}xP^OUvBa!C^&clGHO3nTAKdqPPKZs55`h1T?y60`Q+Ralc~vuA|#G402n z#a?G8i)vHfiBt11_%4^D!4z*((B4bAQZ8kX5em7`Hhb(KSV&Fz9wI*`=E4(e4oHXg z*teZLt@_)_MgQF_^l`(rOua;tJJJ4o(WrTs=BK{|KREm$zDW`_Q%Z9n5H(};(qotH za9ZjK*RgrM!Ps3&H>BQ#=j*kX7!^!w8bAZT*LY?aUZYa~?zMlRK<9*%@&8=so>yx7?+<^whQvlRx-J0X!juYKAh|GcA> z;Bb18^czjJY+8NiMn-QkPfx7boZiP((``oXb97)-0K-a__-j0b9n+^w@cb_iK|6eo>#%EsNaYtGpbqIO=rr4IP#OV zSF=&=l|(ppU^ltb{ww0OyfAa)3a&uD)}Z4Nf>qSbo6WLDgzv07642NM8Y3V!i`Cl}k z=#t#z=P0Vo#bVmySkg(Y3(+m1b-vdA0_&wG4fLJy)1SSi-$oN^YhypuO*4>k3GwnH`wJoUrw`h zIIojw)w9H{Cn^xDS(9mnV)&JD_XY;|UFxR>s%21=7%+nfxkz=BTImiPNalXlmBnN1 z!_^E@jv;j+%+K?hG|O1v(c4#C2Xj+H^u%m zQC)j&ed?EhZrgX+p;ax79bwhaNeNwYG0JTEIcJZqTE8mT>o0Uod!O2ruUo3YmIdcK zkXWsRsyU{yO^`8C8PG7cmWA-A+Gn#Xyza} z&YZ2{w-u6mM;YrKzfZ>;pWD5mtYSYai^E{2nLmSh6JkZJb}}}ITv6hw7=-xaT;v^m zU+reR=#Ch}184RZ(fV7g^zs9)W>0L6f|OkTw0|KK@*AB0Z$TB9-4-n9Z?@!b`FFU# z$jm=toL;Y(#C+gnrr}c})2s~md(^4zGsBvSd>vprYDA$xvkWFMz4w`@erR`H+Ujj3 zBM8ShhSHoj{pJhm5f3@^R{)?qOr2`QR37#8^%ZnoB&w~#S zI!Y=OP)M(A;lU2%_l5p16SSL?)3)jG_2?e= zrFhTl$+MS&ePj0b=E3{!v!$8Oshp1Dk+m&uK8$N-i~SeJ+<)?gr}$rN_=0g%81aEZ zIO;WRt}3j^51O*)l_H+1U=TQWV0!LOHvFyoMR@Z}G-(CYVNwtQD zL(6t@JLTII7q^d#@0^{AxSvoexO&`raW9PZ6KRxMsg#bVI1>M9D38_6FWkS;c~@MH zrBUm@ir{y*m78!m`d&z^%{{(Uf7C``oxEoxF2YC$ z5O#Z%H$Uu+rNpSSuD0S%kI>`swc&@!YV^C=2=UU0Q*W|^Cr3TQ=q<8|PYgak4Y?3(Tpu_5a=zrj_A&yosFVfaA>U+xtBYeH|A=>T(0E1 z`#S%upZEOZkkY%8zC(l;86|FNbtK1Hm7j2{~dG$<<86zFnW^^JPf9w7) zKDk&mlXY+BToEis!m=*2E`gBe`K5EI&-NFs_e*{A@3za_sb-m8C=N`Z-?-u(VooJR{*6GEz%shAH zGca+-NO8k*A5Am6?nM|%pZ77(df`{mJO&l)!P?`I&5u7vel7I=mNfL9_sZx}h-V4% z_gljQR=Dh?Z@FJ)YwBr)9P7+~2?bNuyy+GRKkc_6+Yo;xEEk2B^KK*D3b`HBpeixi zlfT3N3M<5{qE=+#d^Uw}#k+Yj$MH4ff*C*`AefNnU1i>81CXs_e z8lQ7_6V=?8C-k#Wc>a5;HNe{C$8zFmQ|{boHAb&$kKd0H#$kLV|0BS50jc@nkZVY* zHT>$c*GreXg7VUPB&Mi?$|&OaiJt$X1<0WGTg!f5&u10WJN4b7OL2P6wD|XXkB`oT zND@f!;^k#jMuP^`L_AmP+)B_>>*1UfG~dqJUz<`U|A(+`q@H`<{%x)OD9FBV ztnk0C&P`a`-L+>JNI@z$^$rq_VUm0D)Lb#INqc&F-RJq*IN)u`3E!5gk~`s-Xu#T0 z6YiGfJ(T$RcI(`&oG#Uv7T+XGnpoqb!BWKTVPu&g#2=R zq&d7jr4LIOeFF1K>C}OLrJ;~Y4cSCWF`o1j>ph90AS-F6df2n;V^Ax9&}EKw%BnT? zV(?jDve@{YF3iE0u6Ai5V_0?beBf!K&|k9GC5@^VP85p^(wa|3cL8L<+h#00r1zDu zS1m8@-U11Ah!o0EvjjTuZ^i5@n;Nc5nu9JuFQPI)sWG`kpR6@T%Es)hgd=UZ+~4rI z{NRi&G?tEkFhs$~8E*v2`Afi!UNpwXjlMZhqQ0mnwy47P%4%(>q+sKjx@Ph0F}XZC@F`Xmhr{TMb;44@uc9~m zQVeA&{u3Bd0>OlD>a%pFlIE6fPu~`eqksLACWoNQrpPDs%^@CVRW8o~rKEqEJgJyp zrSF9A52s_GYgcUMN7dh$xC&GiB>v}yH3@91^!B;5ywy&IrrB|ccc5Gy>ls7S`nn}C zWZ)^~r2~4#7ht9sW=XuvTlxxv;9zLTC;}xN24jAq^NlZ^ss5#dZ299f!a^iu6q%7f z_CkgaL!3Vb>`bo-sEq{(#P;h=zZ#7^r!%ejIF&FIG>YHV|G`IUDE(g4;PxJmE!~DI z;>OSXr=mmcPBD}tX^kQICd?w$p_tJ$p0hKYl$-EI4c@%CJ)T62pMJS6x2ujpDVVOz zcXG#!sosMzZRDD{*U`Zf{?gi}p*j7GY+C$pIH7}d{3~HPqU(8&lbw6#7;+9UV%6kQ z0uL23ke;>x25qG-GXlGFwkYEaymJ zmHF{B3>HYH<_msq^?l=a`a!AOt+1mrxWZ<5d<^`8j`Q)~T+`lbaK#4Z*PSArk`FcN=V=?^t@f&N06D!W)Q5RCba0xamTjsD<|x)48B`N`046T#$s%!S zUWJOviIoyxx{RMA|B}nmuYJ(1wUP;W?xOHRzh=G zsAz|$nTU>$VrQjl%h2ejDd?|n#2K;gi>>p}yA^XH+oist@vR6{`$sx5ExJ+f+wxVJ z>cyGAPnyT|2)#Qat<@a!0)6Cr6+q(MnbiDF`~LEkNIk77(i}x^Y)zwyja%*r$Ds$ep%=RqTM_xOiF&fn;FUuU9oJE}=Iu1<`B0{5vavo_#JVwKH7X$xru#=0` z!z@+%V!)}kicY3|S+a@%Q}#z~yS_2;~X(S>d}&>wWRKKdlf&x4_>j;^|kKmpM8`}>IxHOein_m2I*A( z*(Sd*0k0dG0_n)oaf|NZ0?Dr69{CE~>X(K7@>%>SowT#RV(v-@0*_uExs+J=*InX4%f}psAM=7r1F%HM#2x)sQEv|O#Ixf$^3<0af7m^$=F
>us_Vpc`@7T43vb^FN}EMye`)Lx+A&79B5WNc!VzHNDZ-R4F; z+qD=fZy0qd@hv_0>5rL4t_*g$KJyT&(rQ6pr)jp-!kf zdAf)$E=Rzc2u73!>=57Jz5VPl|FQvl_T{4T#nyh@j)a8JJU40myobcJp=!FNqKGYq zH$rBwrRC}&03su4AjL0Guf>0dTwoCNd?;`r^0v&A6lyRn;eWHV+Lx%9#vk~q2pg$S zKbDr`kI>&^sMYY2(R1ED{l+?u`}xE$YEB?^{uNYdB7h1-qOftS{jEqH{ssCDgu z))Ydt|A@|@k8|GrVJFDulAu);FJ~ngRNJj-lR;KA#$wzTFu>=Dg^jtEMvV5BjtQJ` z@3sC)d;HtSU#jUC&+?Y>Z(Snit=+It?Ni+N9(qUz%D^zQcx(lRP5tj?Q;sw_R3rI2X|CG@nF!RDY|d z?ZqIVnrG(Ev3)~=Pb5u6tqO)*BWw3&=}QsCdOgtSix>Q~(M?NZ6=W7)-Libm3NGu75*4&{{ zHI&x}4zlkfr&0PRiE+O6tCr<`CR@=#GK>0mb(RVYXrHD2_p83;uYbTX-AKT)gu%SeIlCT`2#8wO?N2%WL%$JT!sXNOl*!T8XyZWwM9+?wM6;@^=lg&qf zs_V)dNQr=lzT;D+r<$Dc?j;yOB!M#w&OFAdiMBX>tiL5Oy0^=*#(k8VY<(X*tAml= zl`HJ!Q4reF6ET27X7#>m>N`>~HkF>ZZchxHP`<~>eLGgT-aoKU%DWe1^8Be|1P1c- zbJbYGyg$=qjI-jJG`=VZM+;SLoWa8Fz4asdCtlLDyeAb)Lf*ZHx;?smp-JLCq}eNd z;%kLNgY;xCptA7D%ynmaS{@pGM&+7y@5K}j0<2AS>zAev#x)i$wcI0c*h0z*Ut~o| zjR?n5*IE&>XqJc8mWbLwWi+O&B2r?69Fzj=F&Q;+1VY(3)wihgowu6A=x5~jq>~ES z3;lF(Ilw;fV6aiq?m_B~)MJ0G*J;hHuO(`+JGTl$N4pXYmD{LJty#uyZw^XBqBy3R zCz`^@Ph&aPvc6~63|#MqL#h%P>57Sw{|Fy`5th$$+m4IxF2}s@eUu^~#HD+d2xe)J za1{*J_PX8Payn8yb-H>gpk)ofPZAjT=|`(?uOxy4W?>Kyy-k5n4m+pfhXgxJP@6a{ z4yjK)K^T1^kHqTL!J@CSu+Np($q{xc+UzG`-6!w#p4}Kt9f|3Qqu|=JU=u458pa~2 zTrn{sHozxE*^NgRYljAWvaG(ira=0_c4~D09s0E9ZIOXvR1sQ&LOY4_N#N~MBU)K= zxo)W(bw?c4CM-To>8W#)8&i8r;?g5)#I4a^ZK`YdZurG784)Yq^KX8Vf$3Q)@yXVoJQYhO9@mS+UQ>VDi(u!?4`WBBS=3*eNBSTDlz>-+{yp80o9SP< z!yTbKZQ^9b?r?ALiTu)PlPdC3ggLx~<8HIT)+m4N$e^Fqma4WmqJBi@R`Mz*Mv7Wb z+OK|#l@Kx+!X}|bOAAGE_RqC=mdBja{tWHq{mH}zH<{WB`_2>XCm)l(*G70@#cI#P zt%^Ii0FY(V&M1(FiIst8G3#|=e{19w@~Q9r$25q`lap*L#A@{V>-W~=h5;7CA=-_J z{cVO8LQbVJB_Hog^M*v<@xT8UxM4_ z|BR%1;(nZ4?QTy&T`nBbu8a>2o@pYf&c)^6k3Hnl%$6W772^m)<>3}j%i}!>d>N$L z_0d;G?qwsxva;HRRWzh*#+BqGr!tWjFkmNr$5=1ax4Ohyyl-E4VrH1#zT+NE z^QbPIL-I58iZRbDFW7Jjq(SY-`xfQ2QCkpG9tLq3`{?kqqFi_n+-iuqIvkCMYU3xZ z83kxQ&jK~K!m;FcweRCZ1iE?V9EmR+RdIgHetnC>L3&%yXZlK_CM%ekNMvDLp0w6_ zs3=8|8Yk`_OsLc*;8hM>Ra;kFH3ToD)E2fHE7i2w!_X{Ne|_|MzS~=df`&pJEA(9Siw6 zj2}EF!7uzZ#y7c}Wj)o|_-iZ3uG<)M!oHlmPRgZb0hJlQ4I*D(_km20uXn|Be*|Mo zLW|qxoy1S73>Nh=54Y7b_p!{uZbn_`pX3D|zh?9L*Ms8!mPXMH?+j|*^Q4BEfr>w) zQ8)R6?3$!hQRNEi+~FyYbFnYfvkx-3dX1nP`~5OGqQR88yhwxH0jw69zOq++^2&v{ z9M}HSDK8tXz99BylhhTCB7|cZKdUd{S#p`RrX3hBEjeIv%TE3js9t&k?;U>bo);28 zrA2FqqZ$DrAH7Un9&Y8I`&sMPE%Q6-+3JsXuISbfc`eTGE8U*XnkQVD`TbT(9*Ppo zQ9x6ym{;IBrp8lk%utN;twPf*{#c3z6N=P762>V~?Sot>ba7VTA(2GEq^uib{(m3o zPJ}1W{TZ6u(-?&}ltE}u>a4ML5z6-^KqU>_lVFaiMc5Uq3%eNW9-DV*vG$%`kjNIp z(Y0(Z53xfG6gtT{Y=sdb!v8@+m`8CqR$7udn_EksrwAD$i3XxD(ivPFnN2dmE@`)# zX@dQ~E~u{8zCw|hf&hXAwvbMy76Au2viuXh%0Y?jmAiLmNG{zUvDHnwt<`=B{`W2* z2&P|lzt%u%<=v6+Y;I~1`9Q%%ls5*(uWyEuCn5Y3m z0FYj;7MZO5Nbr~LhgAK5S3w@6Z<{=)%=l-!d%IMWa&&DA-jT9HRha`(*$ymYd0M1yr5J~eT%2anRBpa=^C_{$zyl@VUaFwQl+O(I_X zGwg2;yOn{=DGTTL(7|eSWV42%nu0Rdki{=_gKRA0vZNg|1KY2R-hNEK3PU8RiomCK z$qh@%x@$vuAm-5#s9!}==T{0sWcz*^1(fBw-(A_y-Y1Pz> zbOL;?9a~@=?+5Yrt9c^c$L@zymW{O*PsXxH_RqRqrWz_)PoXT}^PCXU9FacT&{;J0 zQKKgfrptY<$bNzL%ehpRz+R~fk5g`s|Kl6qu9vHWjk2ZMBD5Gff^#au_+jzW7@DF5-9`6>TwDj_9~S1yb2mzCB%(zT58`(}(e#Gbwl?o- z?lvLAI{M&P;7EASamLA?NYStXP_F^ELp9?km z?l?N!?0>#T$m}x92h-q>{jn6B2@Vs|OdKU(5~Q|7 z`PVHi?%d15EqX18dCc#=VF;Kx{w5|DIxk=MmJx9|+Q!QRN6tmXROK)u?x`}}=l1E<3=3pXZ(qo(uZ@0S3< zvtF)2iawi-n@9hF(jw3(1r@?${Jk*81!3RrF>8mMgcLV0ZE0~3sFQS^!zh^Y5-dXC) zU742*liW;!tK+C;370kJZ04=$*vV@Gy0eNoB4@V+B_-Et5=u zI^rA5ipH6R+K?;wh?I7X5XY`&Tdu~rVlK7$)qeRaenx$Y7rz*dW4y!cmQYk6ZBd6v zj%@(Ul~_InQ3Hx;rC~px1LE~>>mx45wsvE+zaI1Ssr!uV0$gN47z9P!*%;qd?cGFv zGJjtxe(T*F0UyGFV*MJCAk%2kMn9kP=QcU3h-M|PS`zetwU~brlN0pt-bj(YYGUmp zKvs6%yBGUVE@s3!#_WZ2jk=Jxu{9=o{&3akVnDt?)DF5(hxfsvIB4(82E+;4n zh(~{c|CdQ~`-Oz#X2q8p18cFl(w((5om*$kKJz;?xBpOV5ci6yPZ7jA3QqB&?fyy{ z%oc@zZj?}QgZdtVFrNQ#8MIB)X{>T39bU}BUtYsL2>3!<1elw;M979EuR+lphX@ie zCOgXcAPB?f)V6Cdg}_yYfs5QLcpBmz#btHIkvpBH_>y#@N`PG$>3QCfkfo znlX?yoNz`o9LQBSB~S0_M>E!XlkXT7;Br)v4|`_6ahViU>_6C&*tOkW4JnCw*DEdm z%HitCERl#4I#6F2EHD_0Qt7kX8n9*j0_wNi`B%wwqdT%z5iQ(kyx*q0^p%G1Kv(^P zk6wGm>BA}!Y+7IaJKu<3e^)+*f5q2qTNm%L{V*1y>C#Q2n9%wH>j$w+j=+EU!m0DL z8@q1?uiFf}BXzZMQh!XTg^x;E>J02A351fix4B2!nOdrSW<5~-Ow*?#4%&OJHpuLy zL-rdHk{4ZXl;sN$YGo%{w_Z1)-l$QViVMOWHN8r;BVWm=@#ZaPyAXP|5_!e(O%a?` zZy}S`yExPt0Dn~!>VKe)NGbbF>XhxmFRCKvY_rhwddy#bQ301@bL9kNLrg&3J{J$K zgcgv^rFqPc<*~{ys4s_3{TFYaPC!#-oR|iOgT;R|2A^s4f685fJM)s=C4Qe`{OIu- zs;Wr4XE>hekR6!HgR%g~tSS!t%wJb_Kf1D`pwEkO&v8_Hm7XkdPfLeo2-3n zOnH5G7dlUp48=M+@GzIFtP9%HnFmg0C$*K_5RqNqR&iFE>hIR0Y-PB_{3dgX62}Kk z+Q<50RT*uvtmk%9Pi|U`YwwY3L8TIbB8?rkbIWzSJ}=B{N7+koJyq+V0pqb*OZtL=klr zD)3D5xZ|ox8h#Y`F=`z*SeB|iy&%;vkyqgHATdt%jb9~<5{K;>;8Su$5`o1+ImPr* zz^Z1v_)YB(n`?MQjkbjM-!qq6Bomp$VH}t)2eg8OgUCO>&hKOWCgot%+A62T~q^Qrk`Mkj=5Ho^~1Siue=c zTfS?4$zYBq)&LG_MK4B9~>PFY(Foc{_gC*=;q!nX2&a^ZA@UtL{-7x{viz>f2V zJZeC(R#oir)s6NWa~=Tciu94~lgKA6KEM3objBH;9=7U@`P1bw>UU1R(hwrEyal5^ z&eHc*E%yZ?wQD0*OZ}M1lww&O5mEuSo|(7k0aOwHYM7B@l1H^!VbuNvFlFbzKZ*3_ z@0|@%uQPru7CpU#4*pLI055#I*S%o8T9;kJ_7_rQ<}zbHvin8w$XWS{46u{PgJZIZ8f92+udTw6qX5dlw>hXT#^nPZ|ghmM0T zl}TlRio#f^H7;AvGcj2Q02GJ?QJ#)az3r$wyPZL z{s3sB`&o>OI~@)IwRJ<`wj=gGD?K7OJ{YRUuy{6Sv(Ueb!pL&m2=p=XUTT15mqwek zUhYgtuC38~PYFSNGn^Y~!6;exj3@^&0uP5>B(^=LIa+BLib#iuOvI|%leIS*AB>+z z-o{t;dI67ub>nD^IcX;e;3GHqqSG+VuMAEYg!NnR$=Wm>Bj-Mr0G6t6o%V$CK zR@gppX})}WWlN>_^*glDJz>ib=i+{O1zVsb{LfxzIx$e z*+|e8QD3^*SnbJA8M?Z{Fakr*yHl3zD%jCDYoqdrS z%|+;-Z4SGtKMv%0u6X4X>+ivcO6K@6=e9~BT*><&%kYHAPnc?SRV8yzG1>myI8caD zxgLB}d)ZYI?HHfqY%O6BZm2gLVmC6-=vJkTlddZgY9rQ$|59YmY&zk)F+$Bfk}W~J zFwt;J6$8rBVIl4=Z!n!X78iK+wGl=#V9N;4Q-YzC(4^! z6Os6>!}{~YX`sEYyYs$bk0=;@<%aPDZ@3#3fesUSMXSx*k)o`X#HtrVJuIsLu9?cN zNJoMjKz9lb3%rM~84QK{n!Cn#Gu9lafH|R(X*U7uURPsrBxQsA1quJtBfJ`vUGh0h z9BggF9YT(c$7dXu7)Q(NrCX?KUqA#|TrSd`X?B=9HZ%5I6&fLzd=e^`4kiwD znEgL>)n~_5G#&4)MmL_Ux5No7C4(Pw8D3fISmq>ZF!pcXmu^W?&9_3ppNb1hm1UG- z5S|dJWhF7f$8DZ+uIGg!#S7$mJ0+e3rJ?cRt>dy7g402H8CxWGIT3k$YA;2gZy)=H zJPh(kAac3*6`$t{ov9bY8X?<}#}*Emh_3Q*l|eW~R*~7qA;-zu@LS7)DW&k&-+s~i zFHQmhrp1XOql4bab&mgt&6@;zq|~eykw&ND>E=nAPM}FoAW@;xKkhJUy>|M>T~V%Bqs?Kd4<_rL>~^pv4yK&HS+SBq)wbn zC2DI}zfe|7lw)`9K?>Z9{d%#Txd*p?7iBsANqJ3a=|uWEJ{rgalYd=Usmu!qB8DIT z*9YTyL;ZZ}2*u=+iMV-Ly@(8{=Cq#nT#oSP0}+)7yNZ3?P>7|8v=<5#X~HayMtj?^ zD?}4{aJAmYPCCzaWPoBZYLIi6n#wrVk}wsjoA?VO5p#V!Vk|%xTW)TmMAZH>>r#}q z1+Df)F9v2YuHHjfTvd&7!IJ=p}k0*^4X4Q7m*xA3xc#67S(CkwQ+biEKfM~^U{}_kLpOrT zlnz5YsqZpUoLp`M0~Ffk+uW}t5>6HHv5G-omQd*xMUVwXF?^ww=N5@1SNX1*fvq;^ zsq^p8fl#(rwtQRtD~;nWUJ_O2T*a%)Ar(hg@dBUM5`R_N=7jtO-WINsnZ7H1d{_?PIoyCHF2DRQkVUwNt63*F-K(4|5Imm?iKDMn7l%>g$ zY$*l1CR*w_wlJ(0oj%BP0y5jzij9OBMvdcXJtqhAY*4X2b#z2RP|i(bI5oj5c2U@iGK0&OU4McJQP0(q{XFAYmeVXegLnqRq08vtD=? zKm_4*I^5`&n)tTt*y9+iHDbGZa!L2T-KsRv~ zV;-KSKN)^=J0~2Euv>gKPcCK5@sE|ztgE(}lkIT~)iV0l_0JuiK0!<{zdTL;J^_i1 zW$>>!yaY4mXR^`uqUg}S6bQlI!xt38-5HNQ(|%lnVi7Ae!6j$k?(ilCK2GA$Ay2mE zFvv4^2IYB(x~FvuJuSrO+1CG<8Vudw=J2R)LTunoeb@hCkwzcKDJZRPTVFi9E&>B( zy-DEhvqRQ50FWmTGf|<|H*5^}oNS(2s3JtYI#U%3Gm@#=UIIqlxhrl~%W#qo?~bg~ zF(VKV5%QXl<@Ohbv}-8Bza6UM6Rf!)E+Gp@pXiW@*i@k}72DR{SLdfn-RFlBax3_T zxaKoY0m8nwunUS5$uuo8zKI5PA908V_BP`i%(uXvQQ#3^@F|I*yyCOh3YZs5_zy0H zn#OumTNL8#zo?Nb;?LKL5L9fVvW#}68400g%+KGq?j0V+3(;z z_9>UTqU3rwdh9Uga{X^Cnb;p}U0^Ko)7JvMx`*uz0$hQMf@z;p?&e2^SNKAEq6cGm zPjX@kgn12y~ja1jXLQcntO5<4oT4mLHH**ox*zyX!38;SIf%c z{2AXyI|wJGP+3GLX~u2P=3vkh1@c)s#1dV^0Hip^T!}N321;e2_8hH`h5yW*@(Cv?r3xdIzFo_hNKZx{wfDy*l+1Tt_GcYq<@s+(&0h9iBRN90nxXme(?ROOx zI77w+`y&I}xWU34u5*q)2fqdE)+m{5-!y%ojOfNS)r)`#sL7VqCc_*0vWuKJ8|^7d zHIPmE=*?y_>cRCa;BPgz22S{F^0N%|MT@W<%*MHWO0ERwh4(CqltZisX9NZGVON9s zW9906ClYFqw9XOG`sHDP0kGQWvXTKr z(S%i&D3uELb|Ny{pTaaP5yLPEUCO-PPj;YFw?qd_%edYu4jpXcIt4p88{IMAkfM9j zGQvS+SQxQxI*G!%8p3O~Ab1M@*Bg2%)#g#^U@SLp73mS+{JPZJe9tS+`VXK3={Q>G z?J4Iq2o9fBxqlYeAbvknHpUVGITS`)%&V*qk!+WZFrhUoThYID6FWb9q29l%QM&&- z_<|wnC)EsR-QMx-p28++S~YgRfrXTHL#9?gffOVuxBgB>D>6ZccyU`unLa2>`PXKQY*DC+H zzNm{}Jg%O->;3jqGq9_9b)q(1Tz^Z)Y_j3?Tx?7|*Zr<0n1>7W-F3$44cZ^7uD~12 z-8KTEa~~Xzh>^>6rNP!OFr#1J(Us&uF^(fIJm4HSyRnKT8FQj5^lpum6oPp~2^M2V z`)>RpxOgT15t$Zd1iLYCZs`k3|~bF;xKpxE|oq>I?>xu$c> z0_|!AvDLuwT`s-KsKoklhn@Y|5qLY^L)v7h4eEOYR=#UhxU^GWKAf4^>)xP&->)*n zWL-~}G&iZNc&d>H=1GvhU_%;iyq{S`fWTO~^?UJvKENKx_o5(h%)o>xq$tSFSnW;7 zPfQINvm&30glUUxEwJ6*SFuI?N>yjdFJg!gH!_^)Y_nmzamZWFGu=Y7;?ecitaJPH z3Mv44mzix3<`(TNq^Y*?hfJosoL{YZM8B}v_x070`VQW&^Q%L9zKL|#&H7u zxs(M3Lx%^yc-}U!*%GHk&yj5Bg{6JbS=z%3>@&wKqg9KbK|yg)(}ujKtq?Y zT>}_xB;2o#>Msj9^NWY9tq9y`GDz}B?TDBkaKOl@zwJn6Gvb;yn(@-p#urRXaiv&o zbCqAV!$`!!$uRcWi4GSyaRMaUQGNOGRyWs47o%K6w@7etubVE>|M z(uD%&NaSNBPgL(;7!M!3?`&Vd2G@3Z%IRcrYRz){7zYo-Z+>Po>PtQGIrM0^#V-tFut|ghOKBSt&LON+7TX3@EVT zIj-(ke^JAu`%REORIqdcrY+(}bmuTc{38CIgg{D0M>S_&r{2-z8$Ciy2enr~&MbY(Zca+E8?zsV0P90iDG#wjYgZ*i#w(sn6#%MKAo0lcsn4pTL7X zPPz@&jN%Ej@hJHu(wqb?$cfz(MA*m$ z-#%zMrp+u`#A*jX!XNH#m6=Wgf3)I}v4foQl_Q$nFr&E=atPU>yJJg;gMp|<(YVoH zcZDIoJM7LUON%h8u?GKF4+#|R>CE%lHH&-FMgCgrylcUfkD{5C_|AR^)3bkHzIt|h zz4! zE{T>IwOos34jNaFFG!N?E(vqQYXe}g1;P@?uFld5VbokBJ~1dWJQC5h*8nP;7f3(7Eif8Wm;R3F&eJ6^`;r3I_&y9Ded1)`rMbiCpPF5&MIN&5~!;=Lvi z&5{z5Rw*D6Z#+;4kUeLMq^P9Qw>BE1`A>6jj|}e+m|Z zD;N?Dn5Agla*;g~y}6%-{2cRkB!(#-FFRt18cS{6vm+9j7x(w<&^1Y6!q8LbPa6I4 zE?fQe$?1bNd~|a5z~4y+?XN39+i+CsM*)70&%41iv-ZP;x1WVdcTofg^x18<&pRQ@JT?3}X9? z^JDFbYhBvsT@yR2b|7ak>2yF5Z?=-NGj=9N>(7d{#zG0At0Hanwwn;YGIv)r6Ti0@BV0e17 zgDG*zh9-)ST?~+YxGQCHML-$nqpF#xUX~~`Sws`z&HH>m@=Ha<-63j>kNL_@+Kip9 zf}0~<2rP?}$wu!tuBtUGa{f!Q|1oN+vZFsBn@thz@RQh4e3NZVj)J3`WgP*T3#)Jc zTID;abTg+vPr8li;jyB9d;Y*?#;O&xlypi?nX1l(0~-n=X(QjLOQz@k8K7@ zN%BFi*F-I8%A8vY%-R`l>n{bm`e)d(#IPqfpB~@v9ZVeHqev?tn;j81AmHW>ZwWLt@l zU1VSX-o3PzsL<}*D6E~_;?(elmE-_Kn6U#`l6uYtP6D^+nz2*OCE$l}r45yiQOIlV zUG$PQhGz;*yPO`E(5OYL1ay{bsb|uwWaYnD&72+^GnL9qVvmx@xQ~$)Y_dq?Jd<;* z$F^o*um%Kp&KwiB>O=qgorM59SJN(!}XLjiQVzt3&(UA6Uq>YJp@(83jQ7|O_hfS^F|gPZ4S%Oi#2-$0meS1l8M~m$A!99DyT$T@A~6Fdvl#h`BLkzy|IZ>6n9nt z2Y;ioO|pB4Pae2&`FikGKn!8v>P3*p4am$(jF_^T{p9zGwJg8rBvJ5O^q!2NOxc@& z)11sNg*j|2AzxiYP}si=2eZD=o~!KzPJQc_)Q? zn95c6q_xn$a<$Xz@cc$RZ13RRXj-|Y(QpLc!J#ihHc^8!B#_VZ0WU!}Z+^g5Kl)ke zpXtE+K1W>I#olsYJ9V`jU3I-57t_oe$=o-w&j*CE`jymJs*fKSj#yX-JSlnv+azlfM|yK`Fy|KqjoJVTnMso# zSvWeN-h&~QV>%DtBE>q~b?VjSG&zvw0|uZuOO)%u`(46vL@M7WX8mde(sT-OHi~{l zZM`3G9{X555T^sOr{1J2%Y85m{z!|8_#7Osa5=rtfH}0L)Vas|ONpwE+ABjciy3KP zIQ^nJ&xBqNb6g&|NA0j-c;Y-Ayc+1H=zW52zTVEUY{N6_AUJB>7=$QCfq-|Xb>&d| zSA^Y;Vlq63{uM(>6Uf21VfHpy$Sx_mt|Q?Fcx5)wUZ~e@$eBr~gCvA%`UQBIyBjbW zA*6xDc^^^IRzUqooEA)(*pXTvDlmh${FIK}aC8=m{mQVA%cqP7DkFqFcrN8Kpt=|h zY1Tb?eSY3=*0B7~<7xXfR!2R$>}qN@N<3*fd3j_=%^Qb<_j!4)sb_>P%`|;0CjP0C z2Ed`_jb{|hzLJA3+miI3S};un+D9yeG_zDfagnDVpL;YF!|6X)Cl|j?k4KZY_aK+E zjlcxPO}=ks^3+wNT$w+}^)tB`@nqt{s4=lTwA|OrAmD=U#n`mWu>P}uQ+7Zy9V+X_ zuQ1x3teI<|-JaVRs~Zi$;n2FY<*JkNF2syqd)Mcu!Spu5HT|2bIJ~=i)>C6HeS_!d z=*?1P(siFHc7`kz$QRvVT%nMxH3qEnjr*@fQ$j1EI=@rsG_yHwzOd0j`XK_Y#zd!( zMD)}>UXiC>QDwL3tYPqxvr5|oD8~q_KerHI_t(>;*s_hNu@bZ_b zD#Su8&oDYaVEK?Ksf_0W_FTxRwB9S}C|tQqsa}#i%pYgG&;O?d2!pH@7dzDjshkW+ zfP{xN3d?jA7UAiD$W0EZxo#e2F? z=v9-nSs>Td&{NZ%v1B9>kC~@Kz z453!*vpnnd!3mdHCdT2oCQT=t{yNbQoOY+(<}G=$#o0L5@&Brb^r6;6C|uvy_J(5B zk9iwjRJ!qdu=^fOx3<7GjN4UmQ`rb8Ylrc_P{MkMA%^O6WUGWCxsL%)%~kt?pF;Fo zQoG)c6upm|OgT*uM7`{{tyoVGDEulD9>l{qoh+eC69ySc)=3;;0?kzdj8k8rKKoUZJ_=!8Sg?Xn(F?`+$%*gE0SOxWe>hFG8@^j}>7coLa!W*oEC zFh%f&rkt-9ty~z+JI+C^x!Te@zFG&@Oy80}wz=pEYo(Yh4}b|7{Wi8xk$ssNzZ@J} zNH{w~z>(%VjnA?D&fGNg+j5(v8a`b%FoHXceE;ySv@$YovC6D<-VadMs+2U#B@gN3 zF=D9J!%twEVL13&!Eo3yg8uIqNwfZBbH|P!t3WS^{aG^BQJCt&U?RVPK$2y`6SdS- zU&%|2d{pDS6_8(t@3Ym zF;LDXJk1V~T$7`L?R^VJ>5~Nz!xTSzjP&mLZbs2Im6BMiUpxTY>oZi(@;nA4l4{MB zsm@2;eyud>W>!9DfeCcZWt*(H z8fAlHGFp{G_OXMeT+X{v?fTdJ8Su6r=?nALwH9UY*QokbcgY7#<4<`(H#U~PO0LY+ zNFCGTewTtfi!EzHj-LV!NHxPTE7c{vXvkawh+2yDFP2?{F@XVlens=J{#OOyQZDP5 zC-9*XiPt7WSEFRvfWeYS$4f_+Hi|z*(Am!6qd4`()ZF2i^)4%Vr<&pSc>eo*`&8$U z9oiT9jD4{65_48Z-aZUIV$YmW?|n>AFx)Pe_)6?LEje|hoJinQEkN~NB3r-6b7f&W zeM9g=MkKAzPK2#k9V}RrU?nWIwk$oAP54kGgg%-=pm1o9Ia_Kz3;@Dd%5ZUcdi|9e zg!WWBRC&#|J=G2Z`-@N<%wG`@+aMKc5Y)Hsg>k{wsH9{ONP=w#Ra+uNc+aVt^mcUy zwR1r7@htQz`IfWcP{_KgY+F5X@8^Dkm=rm*Z%ejy`UoR3v1agk6@KGx?3ru>M!enJ zb2+-xTTPatTdE7W1^5=^H`_uAEnLJ{dRr28WK2rW1XSqR&C_|@jf(rY+hs*xvhU7s zZxtF063@TSb1>%oOlKjnV;h(qzkKWY>^It^-uxz|m1nwGBfuQaxE+14r@|n94e=}R zW{?otBMq0gwsSRd+tzS?b4 z*2n(%jHD2jgWP&%LDXoLuOWi_O)?zK1s}`dBvJaH=Ln;M5koS{sndF#KP#8sp74&* zGe_8PS)Cr<;5!M6U$05UdLc6A5i`3!8~&}z0Z;xK=1@87?M9BloE8KgOg|nN@-3mK zj>FI(t9|I49Z%>nMy(-doj{8aQ4b-Lkf|eF9yv#F{xAnWjrD@$=}4ponGt`7&#g44 z!2JYq;IBb9t23wVzb!vZD1bn}X4*}{i}1xU=Ntt|eSUDpC$^YmftSHgNmpuEZ0{iK zbERxvQ7>vO+^ezo*$%NEIyJpe>9h)?O!zzMUAe+DhFz?~?Vhu85sW{#E&n{WN5 zoQ3aq)duBF$XAD>dV^7mvBU18+J?S{P0LTD@D?0L%LJ=fGNJX=z*MsH3PqA)>zNii zR=pvVl&*;+00WlhRdJVXo(rrS|4F{{4~IN2U{_mX(tpY1*G~q6F2aBIs`Qm-HxF+1 z9oX1XFssGDFBLO3au+&zEoPY94dzgxKXg1TQ9 zNYn%hDTAB*d#@+XPLFP~cLSDI1`MG{K!R|v)*#99OcbzsmRE8UC}tS@;2;`+82 zvcglIpuEaZrN;RL{wpP%4o~Y{^Z=ayV4>Myu;KrS@&j0C_`g^~R@ll0v4%CVfoUG7 z;-Jt`uQ8M=X`&(`{3pdK{QtsTr}sWZ8{6#u`1d5)f1h-Y{g~#63M-M++vcT8Z%`49 z{_|w;|Mlc9@xNzq;UAqKsI?*Uy%^}}zUwjcM%|ClN~Ro~qt{}=P0N5*Ub)@ghwGxiZ#Kfqt9tAuzhWe_>)dw zr94epFVsC3c+oY0v?qkS^LmUV0?4`>QC`_MjiiUrGkSk!Q1{Wo%4xNDdWLde>Kb&q zH@=z)q`xl1E7M7pM$btSxY2pH_@c6Vg4bz2$YmTd)>=%8dv(bqvRrQdj+E$qDv5nBVTrIi2$mKt zW3e6V4O7A{vk&elZm-Fxf^Ubfs+XHbzIcBCqWy&vcL(d5KXKfx_v3a5L(j#BJ!vyH zU;%~&T2j`X_aW3om6R*_)n@7%jZ&|fT<4Bg8iHc$a+KIw?`(w9bs9e&K6*zvYdT2m zJy^=Z(#U=*)B0^A7=CaD`>~)kdou^@$G@tu?`o5uP3p&4N-J7>8r1@m}a>oQWu zJjOaDdJWq1RneN~zBktaidSSCTe&+&V~KkZozDaTG<`9Tk09L7%k3-z$D;h#w-38y z9EU4em6>8_@%X8-V8d`*6B-6?@gJ6{j-ns}oo?>_Oq$vPlmcLsG%Wxbd_!D6M_mMN zVK}d>VF>XW3JjByX|qH{OnU9;Ig2cnxzSS!IX~Z|SBzt7Tn$TEzq52|9fCpCtqw$^ z+!gOgtUI8et~K`V8uEZ^1&oR)_dg~j_D}(Sr|&Kt)35%0%TXV7dWRo~dtl?S2td9K z5P4-uKe)f%e+}*tdB^cNsw=nTjJ`W1bvBY>AQ`(z`Y+3Q#;cP_vs`5`3GZ_|DU)9a zSyH8hg<^80K!8Vhq9DFVv)i}F_gR=`r5eE4WLpqzi6gnv`hPq@G*tif1if{Z&Ke04 zQjan%-kFT4PbxMhAJWWDraq0W{04?`3Qm1}j;-UQkw~F0(6>eL3P1&UK(9LS+tXxc z>aH;h_z=oJHXXy%S-o8PUDM1K>u>^99LTCgpAHA zCzPsb?b zc>6009y@$NN3AlAg3|9RB(S{zsAWofygNlgyNg5b$OOGvLI5HG*1%i3JX9|*PWic*&k;f4p z{62-_sCC>q&5wZ&H-knx-2S?~o9)wMmCHMW+v{7TJDmRn8lDZ>M?yDx_EPE^-0>At z={fG;G7eTg=!wExFa9jiN^(9H#s=Y$`m{uoX{%qg{ba?TbcB{4QU1;P9~3QDUHCnq zVO zS%Xh0e=KDfv@d$51VEd)Kyj=-(csIZe4^;O7~#Qr+6*ROy#iXn)?|=$dWeU_pZgcL{F6-Q9w_1^3|Y zPH=*|LvZ(vL$Kf)+}$oO-aMW6H#kNaZKslESlZXuSodaSsq?uV)h3*6oh zjGuhBODZ@0G#_H2#J{XKYiV^TW+RW|QCJF$X=KNFFn-&XE^0$DN2g){H=F9zzEKm~ z((f=E+(XP9u0~Qd*t)y5u6^QOu`pZhT^}xapLOtl)Bxo-fpC`VI=Od^TlD!TZ63M^ zSC50vzmVjifF!+RML?rdG4OY*<*~sk+^v?70Drs7oIQ~QwyBTbtKp$z)TdHvgMx7p z5SzZU(1l>HnB=WPd9awH^BU)$T4;_lsAAmhxOx)YO{>C02za(UBMGbe%Y0NlU_cDJ zLa3i0mHP*gE|+w+s2Tpa(|)Sp0T-_dpyyr;psCZYz8upS<^8FzwrM4|*GVMCjOl<7 zj#0^Pp?^()O~u1(m+q$5gZ=XQ_GQWS7MQe3-Uyy@ z!?a{lDc?hXgM5V#WdA1bRbzmv7oPH1|CSe0NegeOWV%E4$Zf;b*B)cn%3uI<-E%Ks z({Dgy*MWTX0Z;8y{O-O@=32B}8nDczV!?cT=R15jKu~0T>D=+;Z+Dgb%*N(m{|GUiBvsLeHMl9TA zkoZ@czEmQX@y&XsS)>M~UzYFHZv`KE&;A@plfe@|wbc4i7s2OYx%i2UA= zQtc83U@0bx-^T;$&njR=W@_kHVhBL;1uoXhJjQ$d2RTJ}-6H59$X=|;k7{`7mk|Guv38! zj=S_rtRAS1tImK4+mOS@?<2p~{ICBFB?GPi>Q`ar%np!SyE#)`Q%qAvT31a9mFc3e zX3>RMk3_UAEqmf$_wn+Lx)G!Y!>LYEm(MZzWfji1a>i{cyL~W zzX%zGWTO5hpZe!Z$T>u-!~>P+WY?coTqu%fD5`ud!~o8=P@=#t(l9>dVf`gp0<_pm z3bSrau6tTU9RQF84c~!f`(hZg`<40=X15PywWr4;Rpvm{U()45uR#v>uylG~Q~(iu z=wz_YF(5K3&J*=ydi)UaTc$$1lHUZMs|2TSbqfvx=*V&P?ITW@XR>L*#S2t#B7QOo zt#w+-`Egub?N<+Sl8?aQZ$cI7`~a-c(74BcG!t-8;TVB85yTmLj#zkso9oPu_jkO+ z$ff#j6?T}2)k7P(<^%&XSfXXSJt%|^4z}R@=*2H~CQAhttU~F?GfVB^^2YIkNB0lT zZM{W7P7RowE$>)gLy{Bc{kbX-0d=E@C?W`?yqf=%i|+J`H>N^0D%ee*!C+8AXpha@ z`zYkV2WUdxJpoEw8|&V$7Xe9yrAYS+;n=5qiXBAhyrD~u@80`TTNTUS{gydx)-!%P zNu`(@@)Y!m#wK5US~UDTz%?_I}J=ESqvs#l~M~oM(;t-!I7Rz07#Zf^r_Ym0L{Oz>Xe}; zAEYcU^Xvd=G@D{>N)k$7N&MRBH??awcBo?5xxLn#ZTnH!^o-7rRe7SmFi2B-;QO|f zR;a9o$ENa%Jcxwo@>^KvX1{cK4fYapAb5)JL@&eDkAPGkm_pjT00C1@I$Xg|>~*A7 zci}0O1hEq|Z})j{#oX6up1NfLFL z4ZndDY@l}epDSZ5>RRxK)(;-@8}9&j2%6vkYJ;;S+np|efgnHiXPAC^Phs@3$zc}d z(0s|K*=+)F1dD0}pH~?FtQ;wHYFg$9`z7gD2|c9Eun8qVgPQpE=t5p>QzP15as;tc z?Qm3;{aK&1h??vP@&5p~dC7?(XsSuH8&oyMx~6 zXPd8D$esw<@7xP!|3OwA4HU2auoq+NNC@YRxK64O*sO}Gga6b+Fp!-^cs{Qebd{~lZ^;K(g#(&GR zZLL(aP+hi>d6dE&Kg4^pKOe59!{ZE?NZXi9q=-Y!(dL&Vi^B(rc6x`%zJKxoSRtiM>+LxCzFJmx0Tj>P$A9(v z&WYP-dH#XCRT%-u+k}0Bbcx^c-J2U^yD+RZB#aA0_31jL3~>}7WH7l+_a@_s>U$2j zJMHKIb5*KS+9f?*kyNI{HjE)gn02llcixO~c0dm!`Mit69tqm=#zd}$^PMZBB2Z61 zoRwQ^0EZTcbOZl#3rY7egkM0_UMMaf;tylyhaEcdm-msgfTXc>{OmdUc&cjtwW0_b zhqp<5GHbOoZpnA-*)6Vi!`j)GZb^)g1tCb%s}1VQtLHm4Fets31(@G_k>BJYJwfeJ z;%+xY1C+7@t#d0jU|c^fEp|m?oP`w5hPz7-4hqFMUB(1aQ!U)K_+Ah@*Z|Nm?L$8x z7~o6HF0B0By$2#=p@D6;YEHr`kp$s>>r7q=34*-9<8PR|hwl?!WqbUqJ24&3n zGQcvX`T91vrsgl{Vza5Tvbx&XBI^LBY%!Ui$CcNjBWrnSG|(u8*hA5R*(?H&;pXNR z=JvX|dOBuyZpQhC{?Dq@W5;>?s+Z(z8ZYnly3?z1Fkovfe%%%btJqA)d3aFf(U86z z&ODG&j~#o4-*ipvE9+{r3Td_39M)k*MmI*>5+a&784k#z6X;Q5B8s8p@v&!=U23nD ze@7n-p^Tk+o@N8a+bRl0W`sT)RpjnFxem4o;W4>$AaL9iWy(RnHx~gxhsAj_0p@F) z5D;#F?N<-F$kQS=3Fu~Q6niA7nZbHiczG2467q$kV13T5+F%+qul7^Gx`M)F)5Tq% z5#$sbPhibsZD0xnFI9+1cP{}k z)jEZD*iuYB34N&zuiDE_hM9nqqY*-6*gTRxUz5QgJQA0xyqrk12{PFAjO43G_hrAV zQ_->G{s}YIRHgW&TtL9P9$^gFufdT*hD3qZ4i90Lo#zWTdk?JQ$r~Tw>yB^;qbfu2 zdY<|xnnTO*LpjO*284Mrh67FUoz2v>jg$V+0a{-45n=aPX}H&_t(v43cy8yy0QAGT zk?OHOeiz$TVxZiPi+ruAr!G(Sds7fhyW3@=Co5#o{jX`FoH_gvo#jfm@}`&|Q{C;7k*_XN zie@&*;(gvzTkXtM_Bx@sjUTn5`{Q!uH2uf z&nXKYV77`>mtSc!Qn03H?T_#xhOYH&*Id_>FAuemJQ8s6)y&E_$-8dD%Ljk2<+` zOppLN)&b1&*dQ?zJ^d*f>h#O-_HNCz)&o=GIca!-%^|RSWAZI ze>JV3er*v(u3O@AT4p2)Y^nZLYsaw`FJ_J%xc37MRufaxJHeUu<(*ZVn8Pav6AWsib{22Lqi8# z`1|vsGii3QpE5c{74%0LlhIx1K=5;(@Y*n2cWJ#*P4rj*A$MrKvZhY?~ z#h<&k{!-?=Ns_e&a{r~j(r1%~K;#e|jOWq+S(XY|UEM&|NUDFaCw@)znF(Ltsh-IB z-&_EUJ;>IkS-W#~cl?{cbru+AN}9N>Nv!($Sc0Tu`*39!nM+d$nWSbM^U}1@Y;`9o{?Px%gWf)J-FEje#q2>+Egs!CmF1vcwh9& z*VM^o;UQ<6Qc;zik{ZXkxW$a{fHrC$qk$ zsfftkA*hC?n`jtyKI9uIpHsg8DPWlR8*Ds?3jG@E&)*+e;(zz|m+AbjrYuUeuHq&M zNm2#;i3b8>-f0Qdwx{nVXxL!-mUp+C2rzmS`dmZ}T*}@Su)K6Qy-b*I=LCEiTkikLoI^vn zf&D4naHS8!NH#RVAE3`xP6vK*%V+RsE;giRR*3*_%N5`7qM$&39k4Z~4pM=$2&z37 z@BuCYWg11`OXy+(pV4*MDO}C=2h0MqKaT)BjP6^&1byIkP|=eFE*J**bTCYraxwUK ze**ubr>6Gt1&9+|tco^|e_w-WL=|ynKZ&<-Lsm(FS{~x0?!l&+?*d|`&qi%#8zcj*9l_&ZDOc=tz~CVYn1NyAVjH` zOgymakCa4;a8?H2DjxVJJ^vP7GKK=T%E<9`S{HaaaJ>I~`KIb@zI=GKdgTmL2n=3n z1Z~#Ilo=&I=@2~M^TmilwphSO5jz&g?6460@X>%@l?m6Rx_b$}flC#kcVK7G4-h)v zQzE^ZC%1JpXza$$dLqz?{Ar z6TH2oM{u4K9q7xF*O-0fjmV+p*L(D$<8Sp5UA_Lhhsgu-<`|IA4lR3QiSC*ZI$!V1 zBTVqiOUOu8X;^e9wU`-C>?^OLy}B`Yb)K|CU**FU(i%~vsunP&rVV?>@$o(0FIH|RmEzn_X}`SDX95y#5{;4% zrFXjg4H?O|7Gi{)StU$lVu|0W;1)xG2k6>E0k~I!-vT}GR-qB&b%6&1emey3i!eR` znL$;Ie`WvZ`ZXo0R0Lj-S!x@o&W^RsgUo-#4Oh-iU!Ww(CtWE>`ZbZ(fcdde(?}+c zgVo~}Pn?Q_v51^*PjTYqo<=#*A~bHyRz0AKa7}jZyCyWk&Y#~Nn$q+l}DPpj`pvg+zy%%4oi1?eG)&(GHT>7PWw`k1N$TaBIO9Ygn{F1 zYv43YlLruuN8O?z_z=n_4lG0gCP-2WeSJYcnF1L~6*1XIqg^#Bp?>)x5C90r# zbEUc;3Il2ed~Vu}Y2#xV{o5wzMdDc38OW_hz;nB;x%5R7jWZULT7?-P*Z(87I&3P$Nj1-c{KkLKq)jE1&x24$nE&y8d<^ z(J#f~Z}E;Xo2APSPqA!7q_x9#ukOw@y}r7HDtqjM?t2tw>8USq%&A=odtcmosq^4G zq9SpPPH2w@ygjZDR;5zFn&pEAzAv9iZc)Z2B=EQ1fzMLp&=AJZXX{-G@E<8L z!ZZDaq(q}98z}TTiw-W)l|#PuE#mLd$X90N;MLE|h+q|b0K_-V=grc$Rg$+pDO7^K z2~GZOEElHGxh>w|zM9FY1pV9%^cnGjA>wM*G-*yeN5K+g`qPFo_DwmR=Nn%M$d%)B ztE2lL`lH(WDl+Y}O#Uj6h0xOTpE4FxMm04{5YAjqwX4(Bb>$ZtCv0X*5XkftA65Kx zAuFG2See&QtO&tP1bpV<$sa}nK|B~1Sf}tP1thi?7|?t%?mdv?lyEJ0kWlZDa&vqi zhv(8y9Zws>+ygdjZ*xl09^pt=U`{*K;Lzz~-Bj-2aI5QZv_9!OuQ4b4i~LkVjZLZ4 zpnjjg3&+kgzSS&m_RU0O2Z!NAF*19tvnpa(yDF4SicBn`0`%=mof~p*LVn%Z=mJ!U zVi}(}gWovxaQ+vUgO%~yGJ;?zLWAZwe#;T%hbmESXiFdwVG&Kj%;`%)uh|3th;}70 zsk8<05)_mNYk|JukykkLkH*E_?AVFbucZ0{_i$1#KvdrN20og;k<-4wPr$iBg#>;JmgAj3x_ zQKyX&zFxc%E_emxi^WQ}XxK>!M<-59y4LPqQ#DsPEN`orFaiMf{UiG-ub&7SC%m$G zZn#qZbQ!Tu`9&F7rXjqUs^C$!elx01*Ir>Sls>1OKA(!duu-6Y2XKc*Oh%3!z}tX> zoCl(%xLG*K1|K+n5X~zmkf4%5U&IAEL(q`y$cB?NJQPo&GjVA2V~ddQ-C+4eHWxA~ z1}g8-+9)^}dNR}z+TzYAc=UigAH$J<=Sa!#+Z6QU*2C(YH(f%hx|TrRF4G#W?p?cO zhK%=Gm}H@>TV7Zvon$2OW)IT|2;SnD}LG` z6YeGeHjK%ae+tb1ooNB1<`7z6cLMC$q1m=zPO*SXNGgP<)C_3IU_*-p0YN2CSm6v4 zxHn-EYDh=MA6levTquC?_kW%ZAZ8NBL>f4`mjPIq_@bOx`|qis=~)Fl0Y377=FyQ~FT;?w_xwkY7zEWqyG z2Pdo7HOcAF0jQ*3zq_MdQHUwN_e$VJ1-$V<8ZXaeBK=>V@beKh?GEPZsW2E^S5Q}9 zTkQh%#|C4GsYUvwa@uK%A9L2b`j)sa4{onZX!TlBO@X%?pGFG2@IZfKw0D?}JwJ+JJZU0dGAvT?h#f<>4$Mw$P8)1fE7>PQo%EUvc3gktKT7g<;fG_!|x@z`i#UpyFn(zALx7y zE-j-k|2_S{2|qN%gjhHE_Y^Q15}yB^KE{0v%@3LzdX?WTuy}z%%m*raW}GJIa4LFK z^=033=Rh>HL*>G%8?Z#^Aaz-C9A495(62tHVtn_di<4gZmZ~b35YVZccVt(U_~_*I zw#QD~i=`Yh;zI4%fP-uq*@-Hdh|+7SRKnxDf1-5MdE8`(Z_5p5u+o*Jj$$E7IG+Qr z9OLWD1qo-}k2r$eqbHgBTpBeY49Z;XOy-C_kv>%?bdr(`B-eLTt&;Sx_QlR`b5$Vz zvJi7p;JKYV4W#e~13~R_bvw%ak?Lnrp5i~n%Jq@Sf4ENI2;Vbi7TSgWMo`KavqP}! zN30n&sAe~%JC`4vr_WW%NKwgRMkJipaH9kK=iTs?4rBj`0pPf$1&&sM7$sd;vT`mG zOp36u9RqT4sbrF|xCgBKq?ZSe6rOK)A1LHfLI`|@YTSs-{E^02}Nw za#q{rlRTpf!xk7PSBiIsQc%K`jQSB&Pk@@;QZ7w()?u4FB8jV&Uv0^`l6<7g(E|JB zPj95>S-WI>z658*LUK?IfT#%y+8pVA#)gpuZMc#e5Ck-Ahhqmn{l24-g*j@<_i-95 z3v!-Kt+m_^Cvpx^VKE+~S+GPE-?h(ruRszcdP-=%RZg;oDX?UsEKqqjADS zzH!^_G;Sp%Kb)LSy9w~4a#XKWFkM1Xv>p%5)1x+gq<1zurX5iO9JO~nJ~w;79JRf) z(H^g6Xt3o=iCA@%rPe{Ma!T8z5MqoZDjvM2Vh0GlRy&5gV>&&!gv}qSwJ2u z*H2PKNpV&7mP@?W4Oi~mbw)8{l6adnovMBNC7|8IX{C6Dm8aR z2J^u_nbxjWXtn0nKBDb*moxQbBAd=dK1|y>YzCZ0^W>~{lp?nPdm}%Jf_AF;V{-XL z41@9Gbdan$wNEXAYV9DL3&{*|uyu51$ptgV6D*1@px9B*2}Pa(jP^%WDE=-Q|IZ z?|bW-IEdJL8;Thvt7P;mKVKlL^lOmo&c4pe$A@lcc(}-pdI_aL`TawKH^IYh5Ii51 z#!pnj7oYE}yM;xoc+^Iirl;u~z<60IQm=Bc`+iXaOqX(h-}l={_m!=<^m0dh*?aA(o-i%EG~+V(LG%>sAkK#H9%}PVxJj266uVIN zFl+JXsnS|{os=6wXr+916rG4Y=u4R;)RuUBgV^D7`$PR@QbsR_$Dmf|g?{$JYdTx) zS_z=C(>^)euP>O3?7puUw^ra~@6 z8le09XHMSZ-QkE8jzf{{+V-ZSZ{p?D-8#7eUa=tik z9|*In<0y=Mt_s2Xs~ljr9PBI7t_2T-`ak|MZopyvKcOB)w7cMYigM=*jcgTSUbx%mg4~6NKCHPXTl}G$Bo$?f_~{YTtpaW5cxl#Z zQoaVxCIEoyT^IAWiQ8EvJEKdih5dVZXV*aC!9d7cQq8Tm3BvHRn7$)T|tnUv$Cv-}2r)%>H zQf!JEUt;n9&232or%s)(gBZejX_a9r1n=!wX!4R`p-@0c4WgBeeWmKyQg!|mtd>uO zIi)>2YDYqkIw3czHJwbRr-77jSkl=vsn1Siyi{&eIU zQ~k0a)kCO`%@8+4(ASMGubf>W-&!rY@Rq?S3bq^nrLyrPQlBEZd&7pjMM4_>+zXt% zhvPME7;I+AD6DHLvN(xm@%(Q|ppH^rqa_K5U_1PIrZrpJ0;#1*j&>{lN#W(&yS0dpo)j1t)D1Kz3 zmgO@L=_JqrY&7Fy7?XNc-E;i1%YvFIX7-w}SR5T^ShUybrQ>8BT4J)e#DO}5*a{K_ z%Ys3e)70@+x7c0)mA3t_5Q+%SF-~t1+ZKiAb8vCcTVtP><+pl-+5H@mH;^qLw` zO9?;)FZ%A3RA0G1Y`H~7xG$_uqioE2>HG+{iK0*fm?&&Mke)flDEt@Np^AT5v8h$Y z>sm{eO25h6q^te-SVEInDKe%ov9z*2yJYjv+F&B)hb!bZt`ZJ1CooY-W-%y#WPIs} zhEK6NY8FK^kUO#7R2psL%!fW?aiX49p#04ldo-pm-b`3*`SGfR^m&!0U-mElN6Yws zr&%O$i!uI^K>I#Vf`s70tO?zVw)>*kt4RSj<9o_(hxe?G`l+ui*9AA*ds=7wVu5Kf z-;9ZMam3|?LMv~fp&K>R+bo9sAAoAy5)f+?>+bf?KRm(z*j-n<#y~4dyE9u|VFd)+ zgvfW_b)kLhxFMeL;reS@zER2N^UvGwFggXQ3x+V27vo$ieWCmSIo4+NNEB*2O8Jvn z3j*On(pf3;A`-`E$?J%Ky|BH@t>ZVQ0fm?0a)dQH2JADHzhdSJ*azO-6_&S)+n4#b zl0(?b*g`K!fSsr?z6`51+4R!N`{DNt)MMloGkcLEDu|&q%`L+-led0v$RdE%(I!uD zG5w)-Y(2(pffMgMZ8hxyLqKrp>!>rbafLVvV~)xvfDN!AFlG#yCnu>suJ);B?-D(w zgKLQ80*%9@f4|xd z8gN}LybV*e*{ndwY?>nUi|EaF%oY%-77D0}NY2{YyY%A{+vfoacI6NVAzo4z|e4cj~5*GC0Js3{qu zguSGIc|xeWqk!fwoXBW_uxP!s623`tgnT!fxOW6!lwv5?$~{2PZd~ks{(zRHNDSl2 zAGv7r3XK#W_xqT$Y~Ctomy1>j5Kgo)2A0g6IeVQ1s6odkbE`z!xR02{ zq9>QU{;{Mfl!z%$E9z{uT@nDDkmS*bob2rTBP)ecCu}W-?g%wg@yxzRA*gXXdYG9pqRFVfn# zNiE1fL2;veYXYtxnKWTjoFCNDl1Zvad7|Qe{gM;&ai1{y_1>MN|6*|oMn>>(HG`9_ z%LTKD)AXO$j{0lPT#JQj2u=O_dSrUv9`z}sg#@V_Qel0o1eUWes!GGhjV|M>n2 zxxrwZ!fKOd~$azi4m_LpJVaBH)#e?}rZ3A-|3C?o0O^=!5-)!oO048;34{4b7% zMLVnNMx>1B!0Q0^kD)VlkB(b`Z!e+WncFUuCCI-<2tkXW@TNG#aDyc6vn^$jtWxJS z>WE5xY3~O^F6}+!QvomJk_&W^)G*o{y=zrmu_cA>uqn+Hw@I(S! z&w=h!$h{=P^wH$Vs(GaPs4Ki4N=01i{B?6epdX>5EPfH)rDiu6)~u91p6k~{ML@bX+yA<#os{rhHcho zLaAJkv;6R#t3U7cZ(GQTmU41E)qqm|Jc*Z~rqhvvwlsi)sldKvs&%U6PfDEDhE7s# z?b3Q#$RXrk<|nn=KEI=oK~_dv6G&ZFNOKMi@K=PKCRn>a#6@0FkaZzbD zTa49kul@-G>E-8RpF1OCASuzLtME^hc^(`-1X9PU_;raF_Fl<#q7~~`&fef>Z|k2( zs-NRp;b?+k3yJ)^&99u7J!B1yq$gJ%qi!qgLfkJaE|b>~fK&aSeSQ||F9~>ghQ}85 zh$ZJeXTHb!c2d$*p_oFVPkgq8cL^CF4M%o_g8PSVo&_`P16w#o%R?VaN>v#$Sv;9! z5oYFhsB}6w?(pBZwnwi`Vnt>?uIE_R7)WYbj5X4+6Zs%`&1>&1)TMzPVbiQT+MSH4ACX)6KX%C9xUwBZ6%> z5oc!^BINYBeiw(O`sVq5rq72>Ij&h9mg_rzA)g`WTneZ^a96*uf?#!lHe2E#{$uFu zf>KK~b>EKw3}Kx>oM!yA-kVTgevoJU@{ifV+k79p;K33OT@(JS9m*(MT)~1%1Y{@^ z8_^-M{?_eJ@}}W}RTRxG=t*7MwAt(`#d<^)%wI4ln|t$KpWj35W0_e|({nPO zzHl zn5LV-X?E0Wn5oc)wxL27=SJeJoeD`Qr&FnqTLS`YK>o8KT;!3Ix)4Y&ATbfn&(O=- zV0UM+D;zjU%23(PZ@TEj(BGwCciHSNdjv^AZCcfPhhcu*Nb+-28oU4RAL^xs`mxA04;dNyc49zwJecIZEq4x&o5MdbjonA|EV&#?w#%um$aG_Y}ZR@d*#* z+#2YdADs*I_X2kty<)3xzWis+xPig*d$iLBxb(=5fm z!4){j7zv;sB1wJ(xhs6Zq1<3W>4WQ&PK9s4=OS^L}E_P56LYytbNb^yAJQ`_=jKZ_+=OH(Gu+ zG}vj*-w1f69#3Qr@IkUaVbCET3`1eX4+(>nk74#p}frtHg? zRL00LYJ^K8R@J;tMc~dzF#daIEwq<`zg>*Lpr$wUdn6no!IztaYdY6FJX0UXyq+J$ zq4>}u^-TcW2{{qB${Ola9Ov!q{$L{$U;hr%>W1GI|Lr3`$T0?i!$Oim`%V?g+iclt z>1~k9)BZ3DU5H`%5<4R66AYmibC?R``rb{Bo_zmk2KIz%D4x3$G<}-1!uiVA>Qvd0 zJ}4KpH&dR0@SO0oY(eP^31^ji@`SNd6-ia~44^rdMwyiDK9zzvf#IG-f~f2~9v*b} zX*@A#ulXgNNUvS$&xn^wW&E|=F3HC!?=LDx{3cko1XEdG-gc{53TFufG(5VyaA6kEu&8lIm5BPwwcPUziq1q(B9z)Pi<3@pbEAZqZDi(V=1ldlw!J8=kTM& zVpGu*18Wqv4wdO{lR^U#a$KxC>3d2i#6%W+60lg^`5+hHx<#CXuAotcK|_x_@Z=0W z&KQt+dOARKY;EzQdtjb=_k*lm!*+?Qf{x$MULzi+M!xH=5czmQF$(&-rN}P5YMl$Q zOnf=&M^-3mhhGG$c~ACB%pRycPus@pOfZhZV*P{}knvofH3Q%xeo3JD(z){DbF(Fm0|JJA293k zELf*4ED5~zq0teyo1-$pw_TYSpqX@)3B7T2UUNYN)fkqBbKN3{fzcbzcoZk^) z(c0jbF`!7!e~HeiWre5zDF8t-B8Ao`_mAg8H?sEBvgF|m$oqM+%9osh1r*3r!7JZ? z!d;W8&C2AmrQ_0a)N-Dr0TN@uAu;L&f&9sX%R1lVGqvHrWo{@3Q8yb}aB>cqpb z7qZ8uA1{Kzdq0*!|AwpuKg>-<%XMQI5?_mjW)8O_TzQ{Crk(54VfXX*U^$`MgyJ3h zG&YWp;4H#4Hnp_uH-Ln)4m%2Ycs-A0(~Rw(tLGf^>)2endyMF_j{+iO)^aWWd^9UR z`MuH`D9EEz;!v636zWRW!N4LcwjBJGu28qzxSQ$WaZa&f69W6({l#Ldt4R(j(-%!~ za2z!OIJ14SRanrO-+{n6E~ayY4y}t1@$w4Pu>eiUi$nIf@8ds^ZEIp&o!;H2PU58&k_cE|T(sPT#t7wV9^P1mf*&(Pi z95voW|Jzhc1g`C!d#l$OG{7|gI1)tEgvrr;?JCEnVVW6xpgunRTqt9OuGmUFA?!Pw z;$N)ueq;%29R=3}u6~YZVs@WTk1=qmObD4RyHbwu5hzSnAu6&)d`iDU~9xWfD$iUNH6oq?FE(7cf z<;QT`@P|&X>(IUCDv{DFEZ`i0w+{J#@1d`jZ{$0&lgyY~1C9Hw4Qmf@!LGJIm%`t@ zl-GC-B=SJVcCljFAXm7YuD7QoX@7k&#NfnmhfroT=Fg`=}3w{dt4ER}s2 zk^>WaBSu7;Z8Zo0;NtJ&+o)E8=5~?2w-vw~@fs^cWYb|HUXgx%(V=D{NiD!2(>(>lxKfSw3qqQ~$0uu(Z*%o z%|l`0d_51KA1R*)#X@{D^$M_gHotgh%>5RfB{fmsM`>!sPYvUE8pq%P# z0re;PlV&3dnZ4h|!P0t*_u`QsnVe0pvrqcM?unR*Z@dQM#{I0Hh@Gx>Z;uylvhTN< zjr!qMaauQ4fs&KY)aP+%f)MEnb@LB_01;@9&(A(=!nXEs`w7L+*VoDYsn4S3L`#RB zT{t}DGagQX5p!+v9HSIXCw;ESHvkeI%@gJRV{KZOphGs-%KJ~!)ce0~Sc6J`t;1KE zJc6y>VhXMJoN5H>-|b&T(p4#`xK`&z5;aoz?%GB~8p&NATHd09F|^$vd#%}0WG_zQ zqO40x$Usg?J*S;*pVHUgtjr%ZzFknH|B^;N3G}CYD|^| z6^V}mTIBlweOgQnu*}v^Ig!{*=pbDzdd*yAd5xcn0UWkVJK~xJ7`6wx z!>?yxE~!qE&PjV!B99-u8qh5j^!+PTAVX0?$;wfdqFnP}TUMtb%Ri!X-7aLCzEH*@ zoPit`5UR2;i8!9zVkkw`=E33*Usj=7`((eXR>9fe)})|CtJsWrn!%fV6s0Al^e5UE z+*#kAI%boat7PixG4Z?8Ewye#A>y**+F352z)|D7d7MYGk^E3n@efsTA~%V% z>vTb8ozeKa%*|==?nt@mXlm!p#x$;sK?S7R3A0Fu%4QqTo>y+VT9bJjpXSp4uvpFp zeeKR!RABv_m`rr@K4E{zE}dB1n`fGMj85s5haquqjrR_Lb`8`H-#(+Ge&rskxDBO1 z)&Tn*<;L}C7Hx^&7`kXkEqp&DM~2a`bQQ>UbKi~MJZy|`BTKf78rKFn)|eTNtc1)F z1e2;zJHIqM;Ojm`?ZJ#ZF1#r`K22QS?Cm-|{@BMwm-A=)Osqh#f&3`1>{K9~K*?s< z3mx%seHx!^Vc$JYN zoj$`{2Fle<09H_+^o9WfLUo?8>fa5g*Wc5RhKJlN=?YR3koi!-JSQ80G&gss+x>{e~J*`4PPiwB723bR&TAL@ViK45tteZOKyFCp4_y@$mIWN?!(|;-H5|?P!-b z&1HU*&d@z^l&rAhsT{frPGg*d5}P(O5}Vaod1`%`v(`J}wVAl3V(s+UPpCivRO+L` z{C8{YA%F~(um9&V{t+g{40|C_4sq}7e>Y}_6BB@rkudh%w_>Pksvp&F%;Jg`#S{!e zj)6e`es1&kL^F?gBw#0iXH;9RJnG#b6TWd8`!iq}5zcsZ+Zt_NgDVHybITEj;Re zmUjNS22E+MUehH1KMh?tmkZ;-_hB{vZRiHz$r}J$M(r_HaO3QnI%@c9DD!^=yhHyGY>e>^LA!a?)u6DyT| zuayrZgk=MA0BSd%$m(|%lY$ST?e^}n`0UkDNJL-ViG_A-D>yKalMwUT-q}7cutFH2#>y8wHn_}8dzzejpqx+S@SD@g z?nI(8ZTbWk`2-5Vq1Bi>z!_qGbICwu`o^9)jms(84X7Kq(?`Sxb(&p#>(SZs(|GL( zJA@zUdl%ZA4b)4BVxUk4kuhk=oNS|L}D0C6%kKb~`C6}^a zD?PcCBsL>R<&w$sl$~R_R8;bD*ih)3u``M<>IPW^Y>r)zmx|5?!2ZE!O|FnLxbOp} zA=I@#3ct(LY+kI%AyKi<(iePhr+;5x0)>3xITHrivu)CIzeY(0y%0^Grs@&AV`7Ip z29PAzL`cHF)w&_wm~JWr2Y8KJ7$#rgUQq<5qj~ zXd@G`&+QkkeV1V933W#2`SkPktIcM}wBf3y`r∈3j>nsQyvI17uhHTNprjD}A(J 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 diff --git a/figures/Pybot_UML.svg b/figures/Pybot_UML.svg new file mode 100644 index 0000000000..bbe38a1f88 --- /dev/null +++ b/figures/Pybot_UML.svg @@ -0,0 +1,3 @@ + + +
Bot
Bot
-super()+load_extensions()+setup_hook()+add_cog()-_load_extensions()
BotBase
BotBase
-api_client: APIClient+api_client()
Filtering
Filtering
-bot : Bot       -filter_lists : dict[str, FilterList]-webhook: discord.Webhook+cog_load()-_fetch_or_generate_filtering_webhook()+on_message()-_retryable_filter_load_error()
APIClient
APIClient
-session: ClientSession-loop: AbstractEventLoop+get()
Edit cog_load() to retry fetching filter list if failed
Edit cog_load() to retry fetching filter list if failed
Added _retryable_filter_load_error()
Added _retryable_filter_load_error()
PythonNews
PythonNews
-bot: Bot+cog_load()-_retryable_site_loaded_error()
Edit cog_load() to retry fetching mailing list if failed
Edit cog_load() to retry fetching mailing list if failed
Added _retryable_site_loaded_error()
Added _retryable_site_loaded_error()
Reminders
Reminders
-bot: Bot-scheduler: Scheduler+cog_load()-_check_error_is_retriable()
Edit cog_load() to retry fetching reminders if failed
Edit cog_load() to retry fetching reminders if failed
Requires
Requires
Bot
Bot
Added _check_error_is_retriable()
Added _check_error_is_retriable()
Superstarify
Superstarify
-bot: Bot+on_member_update()+on_member_join()-_fetch_with_retries()-_check_error_is_retriable()
Edit on_member_update() and on_member_join() to call on _fetch_with_retries to retry fetching infractions if failed
Edit on_member_update() and on_member_join() to call on _fetch_with_retries to retry fetching infractions if failed
Added _fetch_with_retries() and _check_error_is_retriable()
Added _fetch_with_retries() and _check_error_is_retriable()
Added add_cog: Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, including the extension name if available.centralize
Added add_cog: Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, including the extension name if available.centralize
StartupFailureReporter
StartupFailureReporter
+notify()+render()
  • Formats a standardized startup failure message

  • Sends a single aggregated alert to mod-log

  • Does not crash if the log channel is unavailable
  • Formats a standardized startup failure messageSends a single aggregated alert to mod-logDoes not crash if the log channel is unavailable
    Bot
    Bot
    Requires
    Requires
    Requires
    Requires
    APIClient
    APIClient
    Requires
    Requires
    Bot
    Bot
    diff --git a/report.md b/report.md index 620c16016d..43382ddc67 100644 --- a/report.md +++ b/report.md @@ -193,7 +193,7 @@ uv run task test The UML class diagram shows the classes modified/added to resolve the given issue as well as how the classes are related to each other. In the UML there are notes adjacent to classes describing how they were augmented to resolve the issue. -![Pybot_UML.png](figures/Pybot_UML.png) +![Pybot_UML.svg](figures/Pybot_UML.svg) ### Key changes/classes affected From 7f434283cfc8d5605c2737b78ce84d0e7d91a608 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 09:00:39 +0100 Subject: [PATCH 085/115] fix: replace bitmap with svg (#30) --- report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report.md b/report.md index b9da48a13d..2a9bab0ca4 100644 --- a/report.md +++ b/report.md @@ -18,7 +18,7 @@ However, there is no simple way for Discord moderators to be notified if a cog f This issue is mainly related to a few functions in the main `bot.py` file, which is responsible for loading extensions and cogs during startup, as well as the setup functions in each extension. Figure 1 illustrates the flow of the bot's startup process, highlighting where cog initialization occurs and which exceptions are captured and reported. The diagram also indicates the points at which moderator alerting is integrated in our implementation. -![flowchart.png](figures/flowchart.png) +![flowchart.svg](figures/flowchart.svg) ## Onboarding experience From 918120121567299ff377f24bba965da3acb55d19 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 09:01:20 +0100 Subject: [PATCH 086/115] docs: added the team member names #7 --- report.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/report.md b/report.md index 620c16016d..ef4267e081 100644 --- a/report.md +++ b/report.md @@ -6,6 +6,7 @@ - [Did you choose a new project or continue on the previous one?](#did-you-choose-a-new-project-or-continue-on-the-previous-one) - [If you changed the project, how did your experience differ from before?](#if-you-changed-the-project-how-did-your-experience-differ-from-before) - [Setting up the project](#setting-up-the-project) +- [Team Members](#team-members) - [Effort spent](#effort-spent) - [Dependencies and setup tasks](#dependencies-and-setup-tasks) - [Overview of issue(s) and work done](#overview-of-issues-and-work-done) @@ -76,6 +77,16 @@ We spent a significant amount of time trying to understand how the project is st Because the project is tightly integrated with Discord servers, this made it even more challenging, as some functions are triggered exclusively by the Discord API. For example: It was not possible for us to pass `Status Embed` workflow during continuous integration, as the discord channel id was hard-coded directly in the workflow. We believe that adding concrete examples or a high-level architectural diagram would be highly beneficial for newcomers. +## Team Members + +| Name | GitHub | +| --- | --- | +| Apeel | [rippyboii](https://github.com/rippyboii) | +| Josef | [kahoujo1](https://github.com/kahoujo1) | +| Alexander | [a-runebou](https://github.com/a-runebou) | +| Carl | [carlisaksson](https://github.com/carlisaksson) | +| Fabian | [crancker96](https://github.com/crancker96) | + ## Effort spent Estimated effort per team member, in hours: From 25f7aaf891a0aaa9c5e5ae664a6015cd4c2a195b Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 09:04:06 +0100 Subject: [PATCH 087/115] fix: fix svg (#30) --- figures/flowchart.png | Bin 52880 -> 0 bytes figures/flowchart.svg | 3 +++ 2 files changed, 3 insertions(+) delete mode 100644 figures/flowchart.png create mode 100644 figures/flowchart.svg diff --git a/figures/flowchart.png b/figures/flowchart.png deleted file mode 100644 index b90ff7e957f730a1e4d079bd11bff1dad6fc0d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52880 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 diff --git a/figures/flowchart.svg b/figures/flowchart.svg new file mode 100644 index 0000000000..1ba93cad81 --- /dev/null +++ b/figures/flowchart.svg @@ -0,0 +1,3 @@ + + +
    Notify moderators of any errors
    Bot object created
    Event loop starts
    setup_hook() runs
    load_extension() is called - extensions are run in parallell
    setup() for filters extension runs
    cog_add()
    setup() for reminders extension runs
    setup() for superstarify 
    extension runs
    setup() for pythonNews extension runs
    cog_setup()
    cog_add()
    cog_setup()
    cog_add()
    cog_setup()
    cog_add()
    cog_setup()
    All exceptions are gathered and propagated
    After all exceptions have been gathered
    retry setup if failure up to 3 times
    retry setup if failure up to 3 times
    retry setup if failure up to 3 times
    retry setup if failure up to 3 times
    From ffb19058796eb286a5fdd2e872906485a9f97071 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 10:28:19 +0100 Subject: [PATCH 088/115] docs: add expected behavior for requirements (#33) --- report.md | 84 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/report.md b/report.md index 82ae9c10bd..594a7fa9aa 100644 --- a/report.md +++ b/report.md @@ -1,33 +1,36 @@ # Report for assignment 4 ## Table of Contents -- [Project](#project) -- [Onboarding experience](#onboarding-experience) - - [Did you choose a new project or continue on the previous one?](#did-you-choose-a-new-project-or-continue-on-the-previous-one) - - [If you changed the project, how did your experience differ from before?](#if-you-changed-the-project-how-did-your-experience-differ-from-before) - - [Setting up the project](#setting-up-the-project) -- [Team Members](#team-members) -- [Effort spent](#effort-spent) - - [Dependencies and setup tasks](#dependencies-and-setup-tasks) -- [Overview of issue(s) and work done](#overview-of-issues-and-work-done) -- [Requirements for the new feature or requirements affected by functionality being refactored](#requirements-for-the-new-feature-or-requirements-affected-by-functionality-being-refactored) - - [FR-1) Resilient Cog Initialization](#fr-1-resilient-cog-initialization) - - [FR-2) Retry Mechanism for External HTTP calls](#fr-2-retry-mechanism-for-external-http-calls) - - [FR-3) Error logging and monitoring](#fr-3-error-logging-and-monitoring) - - [FR-4) Moderator alert upon failure](#fr-4-moderator-alert-upon-failure) -- [Code changes](#code-changes) - - [Patch](#patch) -- [Test results](#test-results) - - [Output Logs](#output-logs) -- [UML class diagram and its description](#uml-class-diagram-and-its-description) - - [Key changes/classes affected](#key-changesclasses-affected) -- [Design patterns](#design-patterns) -- [Benefits, drawbacks, and limitations (SEMAT kernel)](#benefits-drawbacks-and-limitations-semat-kernel) -- [Overall experience](#overall-experience) - - [What are your main take-aways from this project? What did you learn?](#what-are-your-main-take-aways-from-this-project-what-did-you-learn) - - [How did you grow as a team, using the Essence standard to evaluate yourself?](#how-did-you-grow-as-a-team-using-the-essence-standard-to-evaluate-yourself) - - [How would you put your work in context with best software engineering practice?](#how-would-you-put-your-work-in-context-with-best-software-engineering-practice) - - [Is there something special you want to mention here?](#is-there-something-special-you-want-to-mention-here) +- [Report for assignment 4](#report-for-assignment-4) + - [Table of Contents](#table-of-contents) + - [Project](#project) + - [Architecture and Purpose](#architecture-and-purpose) + - [Onboarding experience](#onboarding-experience) + - [Did you choose a new project or continue on the previous one?](#did-you-choose-a-new-project-or-continue-on-the-previous-one) + - [If you changed the project, how did your experience differ from before?](#if-you-changed-the-project-how-did-your-experience-differ-from-before) + - [Setting up the project](#setting-up-the-project) + - [Team Members](#team-members) + - [Effort spent](#effort-spent) + - [Dependencies and setup tasks:](#dependencies-and-setup-tasks) + - [Overview of issue(s) and work done.](#overview-of-issues-and-work-done) + - [Requirements for the new feature or requirements affected by functionality being refactored](#requirements-for-the-new-feature-or-requirements-affected-by-functionality-being-refactored) + - [FR-1) Resilient Cog Initialization](#fr-1-resilient-cog-initialization) + - [FR-2) Retry Mechanism for External HTTP calls](#fr-2-retry-mechanism-for-external-http-calls) + - [FR-3) Error logging and monitoring](#fr-3-error-logging-and-monitoring) + - [FR-4) Moderator alert upon failure](#fr-4-moderator-alert-upon-failure) + - [Code changes](#code-changes) + - [Patch](#patch) + - [Test results](#test-results) + - [Output Logs:](#output-logs) + - [UML class diagram and its description](#uml-class-diagram-and-its-description) + - [Key changes/classes affected](#key-changesclasses-affected) + - [Design patterns](#design-patterns) + - [Benefits, drawbacks, and limitations (SEMAT kernel)](#benefits-drawbacks-and-limitations-semat-kernel) + - [Overall experience](#overall-experience) + - [What are your main take-aways from this project? What did you learn?](#what-are-your-main-take-aways-from-this-project-what-did-you-learn) + - [How did you grow as a team, using the Essence standard to evaluate yourself?](#how-did-you-grow-as-a-team-using-the-essence-standard-to-evaluate-yourself) + - [How would you put your work in context with best software engineering practice?](#how-would-you-put-your-work-in-context-with-best-software-engineering-practice) + - [Is there something special you want to mention here?](#is-there-something-special-you-want-to-mention-here) ## Project @@ -146,6 +149,11 @@ Identified cogs pertaining to this problem are: If a cog fails to initialize due to a retriable HTTP error or network-related exception, the system shall automatically retry the initialization a finite number of times before giving up. The retry attempts shall use exponential backoff to avoid rapid repeated failures. +Should a cog failed to load at first due to external site error, the cog shall firstly wait for a predefined time, and then try to load again. +If the loading is finished successfully, the setup is completed. +If the error remains, the cog shall wait for a time INITIAL_TIME^(2*(i)), where i is the number of retries, and then try again. +This is repeated a total number of MAX_RETRIES, after which the setup has to finish, either successfully if the last try did not result in an error, or by throwing an Exception. + **Tested by:** - `tests/bot/exts/filtering/test_filtering_cog.py::` - `test_cog_load_retries_then_succeeds` @@ -163,13 +171,31 @@ The retry attempts shall use exponential backoff to avoid rapid repeated failure ### FR-3) Error logging and monitoring All initialization failures shall be logged through the existing logging infrastructure and reported to Sentry. -**Tested by simulating Exception and observing the Sentry output.** +If a cog fails to load during setup (f.e. it throws an Exception), the error shall be observed and notified in the Sentry logging output. +The logging shall be comprised of warnings after each retry, specyfing the affected cog and the number of retris. +Should all retry attempts fail, a description of the error nature and a name of the failed cog shall be logged in the form of an Error. + +**Tested by:** +- simulating Exception in the affected cogs (adding temporary `raise Exception` in the `cog_load()` function) +- calling `await cog_load()` +- observing the Sentry output + - After each failure, a warning is logged informing the maintainers about a temporary failure and the number of retries + - After the final retry, the full error description is logged warning the maintainers about the cog load failure. ### FR-4) Moderator alert upon failure -If a cog fails to initialize after exhausting all retry attempts, the system shall alert the moderators of the server by sending a message to the `mod-log` Discorrd channel indicating the affected cog and failure description. +The system shall keep a list of all extensions and cogs that fail to load. +A cog is added to this list if and only if it exhausts all retry attempts. +Once all the cogs and extensions are loaded, the system shall send a singular message on the `mod-log` Discord channel. +The message shall contain the information about all cogs which failed on startup, as well as a short description of the error nature. +All moderators shall be pinged by the message in order to make sure they are alerted. **Tested by:** - `tests/bot/exts/test_extensions.py` +- simulating Exception in the affected cogs (adding temporary `raise Exception` in the `cog_load()` function) +- calling `await cog_load()` +- observing the `mod-log` channel: + - Once the setup finishes, a single message is send to the channel + - The message pings all the moderators, and specifies which cogs failed to load and why. ## Code changes From c1325f18b44242c643dbf07b6f257f5b9d2d929e Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 10:45:49 +0100 Subject: [PATCH 089/115] docs: add test structure to requirements (#33) --- report.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/report.md b/report.md index 594a7fa9aa..f421565ceb 100644 --- a/report.md +++ b/report.md @@ -168,6 +168,12 @@ This is repeated a total number of MAX_RETRIES, after which the setup has to fin - `test_fetch_retries_then_succeeds` - `test_fetch_fails_after_max_retries` +The tests are structured in the following way: +- The API calls are mocked, allowing for successful and unsuccessful HTTP calls. +- each tests calls `await cog_load()` to start the setup +- If the first call is successful, the cog shall load without throwing an Exception +- If the first call fails, but the second one is successful, the cog shall load without throwing an Exception. Moreover, the `sleep` function shall be called once. +- If all the calls fail, the function shall return an Exception. The `sleep` function shall be called a total of `MAX_TRIES-1` times. ### FR-3) Error logging and monitoring All initialization failures shall be logged through the existing logging infrastructure and reported to Sentry. From 3e875467b2045aaa9ec20f87ae1217f28ec7d395 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:18:40 +0100 Subject: [PATCH 090/115] refactor: add back-off constant (#40) --- bot/constants.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 674ae12fcc..c51d0d1cb3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -462,6 +462,9 @@ class _URLs(_BaseURLs): connect_max_retries: int = 3 connect_cooldown: int = 5 + # Back-off in cog_load + connect_initial_backoff: int = 1 + site_logs_view: str = "https://pythondiscord.com/staff/bot/logs" From 6805c9aac0baeaa319166ae7b9de33267ab4e4d2 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:19:33 +0100 Subject: [PATCH 091/115] refactor: update constants in reminders (#40) --- bot/exts/utils/reminders.py | 9 +++------ tests/bot/exts/utils/test_reminders.py | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 86ab17c76e..e116dcf2ae 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -46,9 +46,6 @@ # the 2000-character message limit. MAXIMUM_REMINDER_MENTION_OPT_INS = 80 -# setup constants when loading -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries -BACKOFF_INITIAL_DELAY = 5 # seconds Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -230,7 +227,7 @@ async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" await self.bot.wait_until_guild_available() # retry fetching reminders with exponential backoff - for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: # response either throws, or is a list of reminders (possibly empty) response = await self.bot.api_client.get( @@ -243,10 +240,10 @@ async def cog_load(self) -> None: log.error(f"Failed to load reminders due to non-retryable error: {e}") raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") - if attempt == MAX_RETRY_ATTEMPTS: + if attempt == URLs.connect_max_retries: log.error("Max retry attempts reached. Failed to load reminders.") raise - await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff + await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 67c8904fba..eb1d903876 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -7,8 +7,6 @@ from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries - class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): """ Tests startup behaviour of the Reminders cog. """ @@ -55,5 +53,5 @@ async def test_reminders_cog_load_fails_after_max_retries(self): await self.cog.cog_load() # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing - self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) + self.assertEqual(mock_sleep.await_count, URLs.connect_max_retries - 1) self.bot.api_client.get.assert_called() From e64ccf4488dcdfdfe3dfc331e2ca7396bc00b9ae Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:22:55 +0100 Subject: [PATCH 092/115] refactor: update constants in filtering (#40) --- bot/exts/filtering/filtering.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index d9cb2c504b..fd6146e405 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -25,7 +25,7 @@ import bot.exts.filtering._ui.filter as filters_ui from bot import constants from bot.bot import Bot -from bot.constants import BaseURLs, Channels, Guild, MODERATION_ROLES, Roles +from bot.constants import BaseURLs, Channels, Guild, MODERATION_ROLES, Roles, URLs from bot.exts.backend.branding._repository import HEADERS, PARAMS from bot.exts.filtering._filter_context import Event, FilterContext from bot.exts.filtering._filter_lists import FilterList, ListType, ListTypeConverter, filter_list_types @@ -65,8 +65,6 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday -FILTER_LOAD_MAX_ATTEMPTS = constants.URLs.connect_max_retries -INITIAL_BACKOFF_SECONDS = 1 async def _extract_text_file_content(att: discord.Attachment) -> str: @@ -111,26 +109,26 @@ async def cog_load(self) -> None: await self.bot.wait_until_guild_available() log.trace("Loading filtering information from the database.") - for attempt in range(1, FILTER_LOAD_MAX_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") break except Exception as error: is_retryable = self._retryable_filter_load_error(error) - is_last_attempt = attempt == FILTER_LOAD_MAX_ATTEMPTS + is_last_attempt = attempt == URLs.connect_max_retries if not is_retryable: raise if is_last_attempt: - log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) + log.exception("Failed to load filtering data after %d attempts.", URLs.connect_max_retries) raise - backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + backoff_seconds = URLs.connect_initial_backoff * (2 ** (attempt - 1)) log.warning( "Failed to load filtering data (attempt %d/%d). Retrying in %d second(s): %s", attempt, - FILTER_LOAD_MAX_ATTEMPTS, + URLs.connect_max_retries, backoff_seconds, error ) From 1e27ddef6ec64f21a11cb75b069446f39e673b52 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:26:35 +0100 Subject: [PATCH 093/115] refactor: update constants in python news (#40) --- bot/exts/info/python_news.py | 14 ++++++-------- tests/bot/exts/info/test_python_news.py | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 2dd607a6ed..d57f28af22 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -13,12 +13,10 @@ from bot import constants from bot.bot import Bot +from bot.constants import URLs from bot.log import get_logger from bot.utils.webhooks import send_webhook -MAX_ATTEMPTS = constants.URLs.connect_max_retries -INITIAL_BACKOFF_SECONDS = 1 - PEPS_RSS_URL = "https://peps.python.org/peps.rss" RECENT_THREADS_TEMPLATE = "https://mail.python.org/archives/list/{name}@python.org/recent-threads" @@ -56,7 +54,7 @@ def _retryable_site_load_error(error: Exception) -> bool: async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" - for attempt in range(1, MAX_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: with sentry_sdk.start_span(description="Fetch mailing lists from site"): response = await self.bot.api_client.get("bot/mailing-lists") @@ -79,18 +77,18 @@ async def cog_load(self) -> None: if not self._retryable_site_load_error(error): raise - if attempt == MAX_ATTEMPTS: + if attempt == URLs.connect_max_retries: log.exception( "Failed to load PythonNews mailing lists after %d attempt(s).", - MAX_ATTEMPTS, + URLs.connect_max_retries, ) raise - backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + backoff_seconds = URLs.connect_initial_backoff * (2 ** (attempt - 1)) log.warning( "Failed to load PythonNews mailing lists (attempt %d/%d). Retrying in %d second(s). Error: %s", attempt, - MAX_ATTEMPTS, + URLs.connect_max_retries, backoff_seconds, error, ) diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py index 273e8d5dc8..d626183e1a 100644 --- a/tests/bot/exts/info/test_python_news.py +++ b/tests/bot/exts/info/test_python_news.py @@ -3,6 +3,7 @@ from pydis_core.site_api import ResponseCodeError +from bot.constants import URLs from bot.exts.info.python_news import PythonNews @@ -73,13 +74,12 @@ async def test_retries_max_times_fails_and_reraises(self): await self.cog.cog_load() # Should try exactly MAX_ATTEMPTS times. - from bot.exts.info import python_news as python_news_module - self.assertEqual(self.bot.api_client.get.await_count, python_news_module.MAX_ATTEMPTS) + self.assertEqual(self.bot.api_client.get.await_count, URLs.connect_max_retries) self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. - self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) + self.assertEqual(mock_sleep.await_count, URLs.connect_max_retries - 1) # Task should never start if load fails. self.mock_fetch_new_media_start.assert_not_called() From 7bfd50d5c6c6b354ca8c626feab82c5ec8720086 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:29:04 +0100 Subject: [PATCH 094/115] refactor: update constants in superstarify (#40) --- bot/exts/moderation/infraction/superstarify.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 180a49d304..d21bd3ef7a 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -21,8 +21,6 @@ from bot.utils import time from bot.utils.messages import format_user -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries -BACKOFF_INITIAL_DELAY = 5 # seconds log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -240,7 +238,7 @@ async def cog_check(self, ctx: Context) -> bool: return await has_any_role(*constants.MODERATION_ROLES).predicate(ctx) async def _fetch_with_retries(self, - retries: int = MAX_RETRY_ATTEMPTS, + retries: int = URLs.connect_max_retries, params: dict[str, str] | None = None) -> list[dict]: """Fetch infractions from the API with retries and exponential backoff.""" for attempt in range(retries): @@ -249,7 +247,7 @@ async def _fetch_with_retries(self, except Exception as e: if attempt == retries - 1 or not self._check_error_is_retriable(e): raise - await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) + await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) return None async def _check_error_is_retriable(self, error: Exception) -> bool: From cc0f3a1643c716ce7249b270cf201a37ccf3f0b5 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:20:23 +0100 Subject: [PATCH 095/115] fix: remove uncalled method (Closes #36) --- bot/exts/filtering/filtering.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index d9cb2c504b..1d2c7b4ce8 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -157,25 +157,6 @@ def _retryable_filter_load_error(error: Exception) -> bool: return isinstance(error, (TimeoutError, OSError)) - async def _alert_mods_filter_load_failure(self, error: Exception, attempts: int) -> None: - """Send an alert to mod-alerts when startup fails after all retry attempts.""" - mod_alerts_channel = self.bot.get_channel(Channels.mod_alerts) - if mod_alerts_channel is None: - log.error("Failed to send filtering startup failure alert: #mod-alerts channel is unavailable.") - return - - error_details = f"{error.__class__.__name__}: {error}" - if isinstance(error, ResponseCodeError): - error_details = f"HTTP {error.status} - {error_details}" - - try: - await mod_alerts_channel.send( - ":warning: Filtering failed to load filter lists during startup " - f"after {attempts} attempt(s). Error: `{error_details}`" - ) - except discord.HTTPException: - log.exception("Failed to send filtering startup failure alert to #mod-alerts.") - def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ Subscribe a filter list to the given events. From 3700aee57a08a756fbf7303310d5aeeecaed2346 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:28:24 +0100 Subject: [PATCH 096/115] refactor: rename variables (Closes #38) --- bot/utils/startup_reporting.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index b3ad78b67f..2ac6da3ec3 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -42,18 +42,18 @@ async def notify(self, bot: Bot, failures: Mapping[str, BaseException], channel_ ping_everyone=True, channel_id=channel_id ) - except Exception as e: - log.exception(f"Failed to send startup failure report: {e}") + except Exception as exception: + log.exception(f"Failed to send startup failure report: {exception}") def render(self, failures: Mapping[str, BaseException]) -> str: """Render a human-readable message from the given failures.""" - keys = sorted(failures.keys()) + failure_keys = sorted(failures.keys()) lines = [] lines.append("The following extension(s) failed to load:") - for k in keys: - e = failures[k] - lines.append(f"- **{k}** - `{type(e).__name__}: {e}`") + for failure_key in failure_keys: + exception = failures[failure_key] + lines.append(f"- **{failure_key}** - `{type(exception).__name__}: {exception}`") return textwrap.dedent(f""" Failed items: From 4d78a8130e0980d0e8fc262c76f3d38741a32368 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:32:22 +0100 Subject: [PATCH 097/115] refactor: simplyfy merging of lines (Closes #39) --- bot/utils/startup_reporting.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index b3ad78b67f..7f2edd1feb 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -1,4 +1,3 @@ -import textwrap from collections.abc import Mapping from dataclasses import dataclass from typing import TYPE_CHECKING @@ -55,7 +54,4 @@ def render(self, failures: Mapping[str, BaseException]) -> str: e = failures[k] lines.append(f"- **{k}** - `{type(e).__name__}: {e}`") - return textwrap.dedent(f""" - Failed items: - {chr(10).join(lines)} - """).strip() + return "\n".join(lines) From 8d4d0a07ee617f05cebcf85b568589a978ca0aa1 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 21:04:48 +0100 Subject: [PATCH 098/115] refactor: remove explicit context helper function (Closes #45) --- bot/bot.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 3d235da920..c271f57e0e 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,6 +1,5 @@ import asyncio import contextlib -import contextvars import types from sys import exception @@ -19,10 +18,6 @@ log = get_logger("bot") -_current_extension: contextvars.ContextVar[str | None] = contextvars.ContextVar( - "current_extension", default=None -) - class StartupError(Exception): """Exception class for startup errors.""" @@ -98,7 +93,7 @@ async def add_cog(self, cog: commands.Cog) -> None: Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, including the extension name if available. """ - extension = _current_extension.get() + extension = cog.__module__ try: await super().add_cog(cog) @@ -121,8 +116,6 @@ async def _load_extensions(self, module: types.ModuleType) -> None: self.all_extensions = walk_extensions(module) async def _load_one(extension: str) -> None: - token = _current_extension.set(extension) - try: await self.load_extension(extension) log.info(f"Extension successfully loaded: {extension}") @@ -132,9 +125,6 @@ async def _load_one(extension: str) -> None: log.exception(f"Failed to load extension: {extension}") raise - finally: - _current_extension.reset(token) - for extension in self.all_extensions: task = scheduling.create_task(_load_one(extension)) self._extension_load_tasks[extension] = task From 7a637c392d4e879935adab705010657f29471ef1 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 21:11:41 +0100 Subject: [PATCH 099/115] refactor: remove dataclass label (Closes #47) --- bot/utils/startup_reporting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index b3ad78b67f..f3a1a3de0f 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -1,6 +1,5 @@ import textwrap from collections.abc import Mapping -from dataclasses import dataclass from typing import TYPE_CHECKING import discord @@ -13,7 +12,6 @@ if TYPE_CHECKING: from bot.bot import Bot -@dataclass(frozen=True) class StartupFailureReporter: """Formats and sends one aggregated startup failure alert to moderators.""" From 4e40408d2d4c9a387b523e26bf30139c7e333567 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 21:12:30 +0100 Subject: [PATCH 100/115] refactor: updated the testcases accordingly #36 --- tests/bot/exts/filtering/test_filtering_cog.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index f9d8536f42..2beb7d8807 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -36,7 +36,6 @@ async def test_cog_load_retries_then_succeeds(self): TimeoutError("temporary timeout"), [], ] - self.cog._alert_mods_filter_load_failure = AsyncMock() with patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: await self.cog.cog_load() @@ -45,7 +44,6 @@ async def test_cog_load_retries_then_succeeds(self): self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") self.assertEqual(mock_sleep.await_count, 2) - self.cog._alert_mods_filter_load_failure.assert_not_awaited() self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() self.cog.collect_loaded_types.assert_called_once_with(None) self.cog.schedule_offending_messages_deletion.assert_awaited_once() From 7e20fee4e95b67db95090d8e2b588043ac5a0de1 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:18:40 +0100 Subject: [PATCH 101/115] refactor: add back-off constant (#40) --- bot/constants.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 674ae12fcc..c51d0d1cb3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -462,6 +462,9 @@ class _URLs(_BaseURLs): connect_max_retries: int = 3 connect_cooldown: int = 5 + # Back-off in cog_load + connect_initial_backoff: int = 1 + site_logs_view: str = "https://pythondiscord.com/staff/bot/logs" From 4b196cc9fb2e50a34d2bc9aa5bf20e95f29f1b3e Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:19:33 +0100 Subject: [PATCH 102/115] refactor: update constants in reminders (#40) --- bot/exts/utils/reminders.py | 9 +++------ tests/bot/exts/utils/test_reminders.py | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 86ab17c76e..e116dcf2ae 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -46,9 +46,6 @@ # the 2000-character message limit. MAXIMUM_REMINDER_MENTION_OPT_INS = 80 -# setup constants when loading -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries -BACKOFF_INITIAL_DELAY = 5 # seconds Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -230,7 +227,7 @@ async def cog_load(self) -> None: """Get all current reminders from the API and reschedule them.""" await self.bot.wait_until_guild_available() # retry fetching reminders with exponential backoff - for attempt in range(1, MAX_RETRY_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: # response either throws, or is a list of reminders (possibly empty) response = await self.bot.api_client.get( @@ -243,10 +240,10 @@ async def cog_load(self) -> None: log.error(f"Failed to load reminders due to non-retryable error: {e}") raise log.warning(f"Attempt {attempt} - Failed to fetch reminders from the API: {e}") - if attempt == MAX_RETRY_ATTEMPTS: + if attempt == URLs.connect_max_retries: log.error("Max retry attempts reached. Failed to load reminders.") raise - await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) # Exponential backoff + await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) # Exponential backoff now = datetime.now(UTC) for reminder in response: is_valid, *_ = self.ensure_valid_reminder(reminder) diff --git a/tests/bot/exts/utils/test_reminders.py b/tests/bot/exts/utils/test_reminders.py index 67c8904fba..eb1d903876 100644 --- a/tests/bot/exts/utils/test_reminders.py +++ b/tests/bot/exts/utils/test_reminders.py @@ -7,8 +7,6 @@ from bot.exts.utils.reminders import Reminders from tests.helpers import MockBot -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries - class RemindersCogLoadTests(unittest.IsolatedAsyncioTestCase): """ Tests startup behaviour of the Reminders cog. """ @@ -55,5 +53,5 @@ async def test_reminders_cog_load_fails_after_max_retries(self): await self.cog.cog_load() # Should have retried MAX_RETRY_ATTEMPTS - 1 times before failing - self.assertEqual(mock_sleep.await_count, MAX_RETRY_ATTEMPTS - 1) + self.assertEqual(mock_sleep.await_count, URLs.connect_max_retries - 1) self.bot.api_client.get.assert_called() From 7d0f75d0c4ec1d6edc39ac711a3b966f3cadb7db Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:22:55 +0100 Subject: [PATCH 103/115] refactor: update constants in filtering (#40) --- bot/exts/filtering/filtering.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index d9cb2c504b..fd6146e405 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -25,7 +25,7 @@ import bot.exts.filtering._ui.filter as filters_ui from bot import constants from bot.bot import Bot -from bot.constants import BaseURLs, Channels, Guild, MODERATION_ROLES, Roles +from bot.constants import BaseURLs, Channels, Guild, MODERATION_ROLES, Roles, URLs from bot.exts.backend.branding._repository import HEADERS, PARAMS from bot.exts.filtering._filter_context import Event, FilterContext from bot.exts.filtering._filter_lists import FilterList, ListType, ListTypeConverter, filter_list_types @@ -65,8 +65,6 @@ HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday -FILTER_LOAD_MAX_ATTEMPTS = constants.URLs.connect_max_retries -INITIAL_BACKOFF_SECONDS = 1 async def _extract_text_file_content(att: discord.Attachment) -> str: @@ -111,26 +109,26 @@ async def cog_load(self) -> None: await self.bot.wait_until_guild_available() log.trace("Loading filtering information from the database.") - for attempt in range(1, FILTER_LOAD_MAX_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") break except Exception as error: is_retryable = self._retryable_filter_load_error(error) - is_last_attempt = attempt == FILTER_LOAD_MAX_ATTEMPTS + is_last_attempt = attempt == URLs.connect_max_retries if not is_retryable: raise if is_last_attempt: - log.exception("Failed to load filtering data after %d attempts.", FILTER_LOAD_MAX_ATTEMPTS) + log.exception("Failed to load filtering data after %d attempts.", URLs.connect_max_retries) raise - backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + backoff_seconds = URLs.connect_initial_backoff * (2 ** (attempt - 1)) log.warning( "Failed to load filtering data (attempt %d/%d). Retrying in %d second(s): %s", attempt, - FILTER_LOAD_MAX_ATTEMPTS, + URLs.connect_max_retries, backoff_seconds, error ) From 1704c9f16673e93e08b519ce3e8c432a732f20fc Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:26:35 +0100 Subject: [PATCH 104/115] refactor: update constants in python news (#40) --- bot/exts/info/python_news.py | 14 ++++++-------- tests/bot/exts/info/test_python_news.py | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index 2dd607a6ed..d57f28af22 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -13,12 +13,10 @@ from bot import constants from bot.bot import Bot +from bot.constants import URLs from bot.log import get_logger from bot.utils.webhooks import send_webhook -MAX_ATTEMPTS = constants.URLs.connect_max_retries -INITIAL_BACKOFF_SECONDS = 1 - PEPS_RSS_URL = "https://peps.python.org/peps.rss" RECENT_THREADS_TEMPLATE = "https://mail.python.org/archives/list/{name}@python.org/recent-threads" @@ -56,7 +54,7 @@ def _retryable_site_load_error(error: Exception) -> bool: async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" - for attempt in range(1, MAX_ATTEMPTS + 1): + for attempt in range(1, URLs.connect_max_retries + 1): try: with sentry_sdk.start_span(description="Fetch mailing lists from site"): response = await self.bot.api_client.get("bot/mailing-lists") @@ -79,18 +77,18 @@ async def cog_load(self) -> None: if not self._retryable_site_load_error(error): raise - if attempt == MAX_ATTEMPTS: + if attempt == URLs.connect_max_retries: log.exception( "Failed to load PythonNews mailing lists after %d attempt(s).", - MAX_ATTEMPTS, + URLs.connect_max_retries, ) raise - backoff_seconds = INITIAL_BACKOFF_SECONDS * (2 ** (attempt - 1)) + backoff_seconds = URLs.connect_initial_backoff * (2 ** (attempt - 1)) log.warning( "Failed to load PythonNews mailing lists (attempt %d/%d). Retrying in %d second(s). Error: %s", attempt, - MAX_ATTEMPTS, + URLs.connect_max_retries, backoff_seconds, error, ) diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py index 273e8d5dc8..d626183e1a 100644 --- a/tests/bot/exts/info/test_python_news.py +++ b/tests/bot/exts/info/test_python_news.py @@ -3,6 +3,7 @@ from pydis_core.site_api import ResponseCodeError +from bot.constants import URLs from bot.exts.info.python_news import PythonNews @@ -73,13 +74,12 @@ async def test_retries_max_times_fails_and_reraises(self): await self.cog.cog_load() # Should try exactly MAX_ATTEMPTS times. - from bot.exts.info import python_news as python_news_module - self.assertEqual(self.bot.api_client.get.await_count, python_news_module.MAX_ATTEMPTS) + self.assertEqual(self.bot.api_client.get.await_count, URLs.connect_max_retries) self.bot.api_client.get.assert_awaited_with("bot/mailing-lists") # Sleeps happen between attempts, so MAX_ATTEMPTS - 1 times. - self.assertEqual(mock_sleep.await_count, python_news_module.MAX_ATTEMPTS - 1) + self.assertEqual(mock_sleep.await_count, URLs.connect_max_retries - 1) # Task should never start if load fails. self.mock_fetch_new_media_start.assert_not_called() From 8428caccda19c9adebb2fdd25eed79cb37fe40c8 Mon Sep 17 00:00:00 2001 From: kahoujo1 Date: Mon, 2 Mar 2026 11:29:04 +0100 Subject: [PATCH 105/115] refactor: update constants in superstarify (#40) --- bot/exts/moderation/infraction/superstarify.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 180a49d304..d21bd3ef7a 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -21,8 +21,6 @@ from bot.utils import time from bot.utils.messages import format_user -MAX_RETRY_ATTEMPTS = URLs.connect_max_retries -BACKOFF_INITIAL_DELAY = 5 # seconds log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" SUPERSTARIFY_DEFAULT_DURATION = "1h" @@ -240,7 +238,7 @@ async def cog_check(self, ctx: Context) -> bool: return await has_any_role(*constants.MODERATION_ROLES).predicate(ctx) async def _fetch_with_retries(self, - retries: int = MAX_RETRY_ATTEMPTS, + retries: int = URLs.connect_max_retries, params: dict[str, str] | None = None) -> list[dict]: """Fetch infractions from the API with retries and exponential backoff.""" for attempt in range(retries): @@ -249,7 +247,7 @@ async def _fetch_with_retries(self, except Exception as e: if attempt == retries - 1 or not self._check_error_is_retriable(e): raise - await asyncio.sleep(BACKOFF_INITIAL_DELAY * (2 ** (attempt - 1))) + await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) return None async def _check_error_is_retriable(self, error: Exception) -> bool: From 0d14e70a052148713eeadbff0a589dea6ef28e3b Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:20:23 +0100 Subject: [PATCH 106/115] fix: remove uncalled method (Closes #36) --- bot/exts/filtering/filtering.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index fd6146e405..af81be2fc1 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -155,25 +155,6 @@ def _retryable_filter_load_error(error: Exception) -> bool: return isinstance(error, (TimeoutError, OSError)) - async def _alert_mods_filter_load_failure(self, error: Exception, attempts: int) -> None: - """Send an alert to mod-alerts when startup fails after all retry attempts.""" - mod_alerts_channel = self.bot.get_channel(Channels.mod_alerts) - if mod_alerts_channel is None: - log.error("Failed to send filtering startup failure alert: #mod-alerts channel is unavailable.") - return - - error_details = f"{error.__class__.__name__}: {error}" - if isinstance(error, ResponseCodeError): - error_details = f"HTTP {error.status} - {error_details}" - - try: - await mod_alerts_channel.send( - ":warning: Filtering failed to load filter lists during startup " - f"after {attempts} attempt(s). Error: `{error_details}`" - ) - except discord.HTTPException: - log.exception("Failed to send filtering startup failure alert to #mod-alerts.") - def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ Subscribe a filter list to the given events. From 02e89ed0c3c32362fb3532765a49d42efd8332d1 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 21:12:30 +0100 Subject: [PATCH 107/115] refactor: updated the testcases accordingly #36 --- tests/bot/exts/filtering/test_filtering_cog.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index f9d8536f42..2beb7d8807 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -36,7 +36,6 @@ async def test_cog_load_retries_then_succeeds(self): TimeoutError("temporary timeout"), [], ] - self.cog._alert_mods_filter_load_failure = AsyncMock() with patch("bot.exts.filtering.filtering.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: await self.cog.cog_load() @@ -45,7 +44,6 @@ async def test_cog_load_retries_then_succeeds(self): self.assertEqual(self.bot.api_client.get.await_count, 3) self.bot.api_client.get.assert_awaited_with("bot/filter/filter_lists") self.assertEqual(mock_sleep.await_count, 2) - self.cog._alert_mods_filter_load_failure.assert_not_awaited() self.cog._fetch_or_generate_filtering_webhook.assert_awaited_once() self.cog.collect_loaded_types.assert_called_once_with(None) self.cog.schedule_offending_messages_deletion.assert_awaited_once() From 955682dbd8933c2775f0dac6a0cd6964c90f9b5f Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:28:24 +0100 Subject: [PATCH 108/115] refactor: rename variables (Closes #38) --- bot/utils/startup_reporting.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index b3ad78b67f..2ac6da3ec3 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -42,18 +42,18 @@ async def notify(self, bot: Bot, failures: Mapping[str, BaseException], channel_ ping_everyone=True, channel_id=channel_id ) - except Exception as e: - log.exception(f"Failed to send startup failure report: {e}") + except Exception as exception: + log.exception(f"Failed to send startup failure report: {exception}") def render(self, failures: Mapping[str, BaseException]) -> str: """Render a human-readable message from the given failures.""" - keys = sorted(failures.keys()) + failure_keys = sorted(failures.keys()) lines = [] lines.append("The following extension(s) failed to load:") - for k in keys: - e = failures[k] - lines.append(f"- **{k}** - `{type(e).__name__}: {e}`") + for failure_key in failure_keys: + exception = failures[failure_key] + lines.append(f"- **{failure_key}** - `{type(exception).__name__}: {exception}`") return textwrap.dedent(f""" Failed items: From 831058899cf21fd966c0962be76edaf295eefc98 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 20:32:22 +0100 Subject: [PATCH 109/115] refactor: simplyfy merging of lines (Closes #39) --- bot/utils/startup_reporting.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index 2ac6da3ec3..0ec1c051c4 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -1,4 +1,3 @@ -import textwrap from collections.abc import Mapping from dataclasses import dataclass from typing import TYPE_CHECKING @@ -55,7 +54,4 @@ def render(self, failures: Mapping[str, BaseException]) -> str: exception = failures[failure_key] lines.append(f"- **{failure_key}** - `{type(exception).__name__}: {exception}`") - return textwrap.dedent(f""" - Failed items: - {chr(10).join(lines)} - """).strip() + return "\n".join(lines) From 1524affb1e84821bd8c6ff8b324820c3c419b4b1 Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 21:04:48 +0100 Subject: [PATCH 110/115] refactor: remove explicit context helper function (Closes #45) --- bot/bot.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 3d235da920..c271f57e0e 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,6 +1,5 @@ import asyncio import contextlib -import contextvars import types from sys import exception @@ -19,10 +18,6 @@ log = get_logger("bot") -_current_extension: contextvars.ContextVar[str | None] = contextvars.ContextVar( - "current_extension", default=None -) - class StartupError(Exception): """Exception class for startup errors.""" @@ -98,7 +93,7 @@ async def add_cog(self, cog: commands.Cog) -> None: Override of `BotBase.add_cog` to capture and log any exceptions raised during cog loading, including the extension name if available. """ - extension = _current_extension.get() + extension = cog.__module__ try: await super().add_cog(cog) @@ -121,8 +116,6 @@ async def _load_extensions(self, module: types.ModuleType) -> None: self.all_extensions = walk_extensions(module) async def _load_one(extension: str) -> None: - token = _current_extension.set(extension) - try: await self.load_extension(extension) log.info(f"Extension successfully loaded: {extension}") @@ -132,9 +125,6 @@ async def _load_one(extension: str) -> None: log.exception(f"Failed to load extension: {extension}") raise - finally: - _current_extension.reset(token) - for extension in self.all_extensions: task = scheduling.create_task(_load_one(extension)) self._extension_load_tasks[extension] = task From b5fc5b21138adaf98f23cbfec3d3b472c8e5d6bb Mon Sep 17 00:00:00 2001 From: Alexander Runebou Date: Mon, 2 Mar 2026 21:11:41 +0100 Subject: [PATCH 111/115] refactor: remove dataclass label (Closes #47) --- bot/utils/startup_reporting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bot/utils/startup_reporting.py b/bot/utils/startup_reporting.py index 0ec1c051c4..f7713d1ca4 100644 --- a/bot/utils/startup_reporting.py +++ b/bot/utils/startup_reporting.py @@ -1,5 +1,4 @@ from collections.abc import Mapping -from dataclasses import dataclass from typing import TYPE_CHECKING import discord @@ -12,7 +11,6 @@ if TYPE_CHECKING: from bot.bot import Bot -@dataclass(frozen=True) class StartupFailureReporter: """Formats and sends one aggregated startup failure alert to moderators.""" From 4d73aadfa04db456d855511e4d49d5694ad58f9a Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 23:19:41 +0100 Subject: [PATCH 112/115] refactor: Move the duplicated retry classifier out of the cogs into a shared utility #50 --- bot/exts/filtering/filtering.py | 11 ++----- bot/exts/info/python_news.py | 9 ++---- bot/utils/retry.py | 9 ++++++ .../bot/exts/filtering/test_filtering_cog.py | 21 -------------- tests/bot/exts/info/test_python_news.py | 19 ------------ tests/bot/utils/test_retry.py | 29 +++++++++++++++++++ 6 files changed, 42 insertions(+), 56 deletions(-) create mode 100644 bot/utils/retry.py create mode 100644 tests/bot/utils/test_retry.py diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index af81be2fc1..719e0ad796 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -56,6 +56,7 @@ from bot.utils.channel import is_mod_channel from bot.utils.lock import lock_arg from bot.utils.message_cache import MessageCache +from bot.utils.retry import is_retryable_api_error log = get_logger(__name__) @@ -114,7 +115,7 @@ async def cog_load(self) -> None: raw_filter_lists = await self.bot.api_client.get("bot/filter/filter_lists") break except Exception as error: - is_retryable = self._retryable_filter_load_error(error) + is_retryable = is_retryable_api_error(error) is_last_attempt = attempt == URLs.connect_max_retries if not is_retryable: @@ -147,14 +148,6 @@ async def cog_load(self) -> None: await self.schedule_offending_messages_deletion() self.weekly_auto_infraction_report_task.start() - @staticmethod - def _retryable_filter_load_error(error: Exception) -> bool: - """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" - if isinstance(error, ResponseCodeError): - return error.status in (408, 429) or error.status >= 500 - - return isinstance(error, (TimeoutError, OSError)) - def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ Subscribe a filter list to the given events. diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index d57f28af22..437e44cd38 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -15,6 +15,7 @@ from bot.bot import Bot from bot.constants import URLs from bot.log import get_logger +from bot.utils.retry import is_retryable_api_error from bot.utils.webhooks import send_webhook PEPS_RSS_URL = "https://peps.python.org/peps.rss" @@ -46,12 +47,6 @@ def __init__(self, bot: Bot): self.webhook: discord.Webhook | None = None self.seen_items: dict[str, set[str]] = {} - @staticmethod - def _retryable_site_load_error(error: Exception) -> bool: - if isinstance(error, ResponseCodeError): - return error.status in (408, 429) or error.status >= 500 - return isinstance(error, (TimeoutError, OSError)) - async def cog_load(self) -> None: """Load all existing seen items from db and create any missing mailing lists.""" for attempt in range(1, URLs.connect_max_retries + 1): @@ -74,7 +69,7 @@ async def cog_load(self) -> None: return except Exception as error: - if not self._retryable_site_load_error(error): + if not is_retryable_api_error(error): raise if attempt == URLs.connect_max_retries: diff --git a/bot/utils/retry.py b/bot/utils/retry.py new file mode 100644 index 0000000000..342897f381 --- /dev/null +++ b/bot/utils/retry.py @@ -0,0 +1,9 @@ +from pydis_core.site_api import ResponseCodeError + + +def is_retryable_api_error(error: Exception) -> bool: + """Return whether an API error is temporary and worth retrying.""" + if isinstance(error, ResponseCodeError): + return error.status in (408, 429) or error.status >= 500 + + return isinstance(error, (TimeoutError, OSError)) diff --git a/tests/bot/exts/filtering/test_filtering_cog.py b/tests/bot/exts/filtering/test_filtering_cog.py index 2beb7d8807..736c4bf1aa 100644 --- a/tests/bot/exts/filtering/test_filtering_cog.py +++ b/tests/bot/exts/filtering/test_filtering_cog.py @@ -1,8 +1,6 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch -from pydis_core.site_api import ResponseCodeError - from bot.exts.filtering.filtering import Filtering @@ -77,22 +75,3 @@ async def test_retries_three_times_fails_and_reraises(self): self.cog._fetch_or_generate_filtering_webhook.assert_not_awaited() self.cog.schedule_offending_messages_deletion.assert_not_awaited() self.mock_weekly_task_start.assert_not_called() - - def test_retryable_filter_load_error(self): - """`_retryable_filter_load_error` should classify temporary failures as retryable.""" - test_cases = ( - (ResponseCodeError(MagicMock(status=408)), True), - (ResponseCodeError(MagicMock(status=429)), True), - (ResponseCodeError(MagicMock(status=500)), True), - (ResponseCodeError(MagicMock(status=503)), True), - (ResponseCodeError(MagicMock(status=400)), False), - (ResponseCodeError(MagicMock(status=404)), False), - (TimeoutError("timeout"), True), - (OSError("os error"), True), - (AttributeError("attr"), False), - (ValueError("value"), False), - ) - - for error, expected_retryable in test_cases: - with self.subTest(error=error): - self.assertEqual(self.cog._retryable_filter_load_error(error), expected_retryable) diff --git a/tests/bot/exts/info/test_python_news.py b/tests/bot/exts/info/test_python_news.py index d626183e1a..75c9e386aa 100644 --- a/tests/bot/exts/info/test_python_news.py +++ b/tests/bot/exts/info/test_python_news.py @@ -84,25 +84,6 @@ async def test_retries_max_times_fails_and_reraises(self): # Task should never start if load fails. self.mock_fetch_new_media_start.assert_not_called() - def test_retryable_python_news_load_error(self): - """`_retryable_site_load_error` should classify temporary failures as retryable.""" - test_cases = ( - (ResponseCodeError(MagicMock(status=408)), True), - (ResponseCodeError(MagicMock(status=429)), True), - (ResponseCodeError(MagicMock(status=500)), True), - (ResponseCodeError(MagicMock(status=503)), True), - (ResponseCodeError(MagicMock(status=400)), False), - (ResponseCodeError(MagicMock(status=404)), False), - (TimeoutError("timeout"), True), - (OSError("os error"), True), - (AttributeError("attr"), False), - (ValueError("value"), False), - ) - - for error, expected_retryable in test_cases: - with self.subTest(error=error): - self.assertEqual(self.cog._retryable_site_load_error(error), expected_retryable) - async def test_cog_load_does_not_retry_non_retryable_error(self): """`cog_load` should not retry when the error is non-retryable.""" # 404 should be considered non-retryable by your predicate. diff --git a/tests/bot/utils/test_retry.py b/tests/bot/utils/test_retry.py new file mode 100644 index 0000000000..8ce6f6db0c --- /dev/null +++ b/tests/bot/utils/test_retry.py @@ -0,0 +1,29 @@ +import unittest +from unittest.mock import MagicMock + +from pydis_core.site_api import ResponseCodeError + +from bot.utils.retry import is_retryable_api_error + + +class RetryTests(unittest.TestCase): + """Tests for retry classification helpers.""" + + def test_is_retryable_api_error(self): + """`is_retryable_api_error` should classify temporary failures as retryable.""" + test_cases = ( + (ResponseCodeError(MagicMock(status=408)), True), + (ResponseCodeError(MagicMock(status=429)), True), + (ResponseCodeError(MagicMock(status=500)), True), + (ResponseCodeError(MagicMock(status=503)), True), + (ResponseCodeError(MagicMock(status=400)), False), + (ResponseCodeError(MagicMock(status=404)), False), + (TimeoutError("timeout"), True), + (OSError("os error"), True), + (AttributeError("attr"), False), + (ValueError("value"), False), + ) + + for error, expected_retryable in test_cases: + with self.subTest(error=error): + self.assertEqual(is_retryable_api_error(error), expected_retryable) From 695206e9fd13405b0dd740ca6179764c730cb356 Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Mon, 2 Mar 2026 23:57:12 +0100 Subject: [PATCH 113/115] refactor: Updated superstarify.py to use the shared retry helper from retry.py #53 --- bot/exts/moderation/infraction/superstarify.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index d21bd3ef7a..5dacf1bb72 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -7,7 +7,6 @@ from discord import Embed, Member from discord.ext.commands import Cog, Context, command, has_any_role from discord.utils import escape_markdown -from pydis_core.site_api import ResponseCodeError from pydis_core.utils.members import get_or_fetch_member from bot import constants @@ -20,6 +19,7 @@ from bot.log import get_logger from bot.utils import time from bot.utils.messages import format_user +from bot.utils.retry import is_retryable_api_error log = get_logger(__name__) NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy" @@ -245,18 +245,11 @@ async def _fetch_with_retries(self, try: return await self.bot.api_client.get("bot/infractions", params=params) except Exception as e: - if attempt == retries - 1 or not self._check_error_is_retriable(e): + if attempt == retries - 1 or not is_retryable_api_error(e): raise await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) return None - async def _check_error_is_retriable(self, error: Exception) -> bool: - """Return whether loading filter lists failed due to some temporary error, thus retrying could help.""" - if isinstance(error, ResponseCodeError): - return error.status in (408, 429) or error.status >= 500 - - return isinstance(error, (TimeoutError, OSError)) - async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) From 791bb8ec3b2c260c961a00960d28b91a4e8436fe Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Tue, 3 Mar 2026 00:26:36 +0100 Subject: [PATCH 114/115] refactor: updated _fetch_with_retries to use logic from filterign #55 --- bot/exts/moderation/infraction/superstarify.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 5dacf1bb72..01481d1f68 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -241,15 +241,19 @@ async def _fetch_with_retries(self, retries: int = URLs.connect_max_retries, params: dict[str, str] | None = None) -> list[dict]: """Fetch infractions from the API with retries and exponential backoff.""" - for attempt in range(retries): + if retries < 1: + raise ValueError("retries must be at least 1") + + for attempt in range(1, retries + 1): try: return await self.bot.api_client.get("bot/infractions", params=params) except Exception as e: - if attempt == retries - 1 or not is_retryable_api_error(e): + if attempt == retries or not is_retryable_api_error(e): raise await asyncio.sleep(URLs.connect_initial_backoff * (2 ** (attempt - 1))) return None + async def setup(bot: Bot) -> None: """Load the Superstarify cog.""" await bot.add_cog(Superstarify(bot)) From c2a5cbd8ba85f19c6024abbc4bbc92dddb9ea3ff Mon Sep 17 00:00:00 2001 From: Apeel Subedi Date: Tue, 3 Mar 2026 01:12:08 +0100 Subject: [PATCH 115/115] docs: fixed test file paths #7 --- report.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/report.md b/report.md index f421565ceb..b002154a04 100644 --- a/report.md +++ b/report.md @@ -140,10 +140,10 @@ Scope (functionality and code affected). Cogs that depend on external HTTP services shall handle connection errors and HTTP failures during `cog_load()` without failing silently. If the external service is unavailable, the cog must not terminate initialization without reporting the failure. Identified cogs pertaining to this problem are: -- `bot/ext/filtering/filtering.py` -- `bot/ext/utils/reminders.py` -- `bot/ext/info/python_news.py` -- `bot/ext/moderation/infraction/superstarify.py` +- `bot/exts/filtering/filtering.py` +- `bot/exts/utils/reminders.py` +- `bot/exts/info/python_news.py` +- `bot/exts/moderation/infraction/superstarify.py` ### FR-2) Retry Mechanism for External HTTP calls If a cog fails to initialize due to a retriable HTTP error or network-related exception, the system shall automatically retry the initialization a finite number of times before giving up. @@ -157,7 +157,7 @@ This is repeated a total number of MAX_RETRIES, after which the setup has to fin **Tested by:** - `tests/bot/exts/filtering/test_filtering_cog.py::` - `test_cog_load_retries_then_succeeds` - - `test_retries_three_times_fails_and_alerts` + - `test_retries_three_times_fails_and_reraises` - `tests/bot/exts/utils/test_reminders.py::` - `test_reminders_cog_load_retries_after_initial_exception` - `test_reminders_cog_load_fails_after_max_retries`