From f9f58e5a574877c488cc3554bd498cd527efa017 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 3 Mar 2022 19:30:22 +0000 Subject: Surface apply_monkey_patches directly in the util namespace --- botcore/utils/__init__.py | 21 +++++++++- botcore/utils/_monkey_patches.py | 72 ++++++++++++++++++++++++++++++++++ botcore/utils/monkey_patches.py | 83 ---------------------------------------- 3 files changed, 91 insertions(+), 85 deletions(-) create mode 100644 botcore/utils/_monkey_patches.py delete mode 100644 botcore/utils/monkey_patches.py diff --git a/botcore/utils/__init__.py b/botcore/utils/__init__.py index 890f30da..07ded04d 100644 --- a/botcore/utils/__init__.py +++ b/botcore/utils/__init__.py @@ -1,14 +1,31 @@ """Useful utilities and tools for Discord bot development.""" -from botcore.utils import (caching, channel, extensions, logging, members, monkey_patches, regex, scheduling) +from botcore.utils import _monkey_patches, caching, channel, extensions, logging, members, regex, scheduling + + +def apply_monkey_patches() -> None: + """ + Applies all common monkey patches for our bots. + + Patches :obj:`disnake.ext.commands.Command` and :obj:`disnake.ext.commands.Group` to support root aliases. + A ``root_aliases`` keyword argument is added to these two objects, which is a sequence of alias names + that will act as top-level groups rather than being aliases of the command's group. + + It's stored as an attribute also named ``root_aliases`` + + Patches disnake's internal ``send_typing`` method so that it ignores 403 errors from Discord. + When under heavy load Discord has added a CloudFlare worker to this route, which causes 403 errors to be thrown. + """ + _monkey_patches._apply_monkey_patches() + __all__ = [ + apply_monkey_patches, caching, channel, extensions, logging, members, - monkey_patches, regex, scheduling, ] diff --git a/botcore/utils/_monkey_patches.py b/botcore/utils/_monkey_patches.py new file mode 100644 index 00000000..89238756 --- /dev/null +++ b/botcore/utils/_monkey_patches.py @@ -0,0 +1,72 @@ +"""Contains all common monkey patches, used to alter disnake to fit our needs.""" + +import logging +from datetime import datetime, timedelta +from functools import partial, partialmethod + +from disnake import Forbidden, http +from disnake.ext import commands + +log = logging.getLogger(__name__) + + +class _Command(commands.Command): + """ + A :obj:`disnake.ext.commands.Command` subclass which supports root aliases. + + A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as + top-level commands rather than being aliases of the command's group. It's stored as an attribute + also named ``root_aliases``. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a command must be a list or a tuple of strings.") + + +class _Group(commands.Group, _Command): + """ + A :obj:`disnake.ext.commands.Group` subclass which supports root aliases. + + A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as + top-level groups rather than being aliases of the command's group. It's stored as an attribute + also named ``root_aliases``. + """ + + +def _patch_typing() -> None: + """ + Sometimes Discord turns off typing events by throwing 403s. + + Handle those issues by patching disnake's internal ``send_typing`` method so it ignores 403s in general. + """ + log.debug("Patching send_typing, which should fix things breaking when Discord disables typing events. Stay safe!") + + original = http.HTTPClient.send_typing + last_403 = None + + async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001 + nonlocal last_403 + if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5): + log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.") + return + try: + await original(self, channel_id) + except Forbidden: + last_403 = datetime.utcnow() + log.warning("Got a 403 from typing event!") + + http.HTTPClient.send_typing = honeybadger_type + + +def _apply_monkey_patches() -> None: + """This is surfaced directly in botcore.utils.apply_monkey_patches().""" + commands.command = partial(commands.command, cls=_Command) + commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=_Command) + + commands.group = partial(commands.group, cls=_Group) + commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=_Group) + _patch_typing() diff --git a/botcore/utils/monkey_patches.py b/botcore/utils/monkey_patches.py deleted file mode 100644 index abbb37a5..00000000 --- a/botcore/utils/monkey_patches.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Contains all common monkey patches, used to alter disnake to fit our needs.""" - -import logging -from datetime import datetime, timedelta -from functools import partial, partialmethod - -from disnake import Forbidden, http -from disnake.ext import commands - -log = logging.getLogger(__name__) - - -class _Command(commands.Command): - """ - A :obj:`disnake.ext.commands.Command` subclass which supports root aliases. - - A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as - top-level commands rather than being aliases of the command's group. It's stored as an attribute - also named ``root_aliases``. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.root_aliases = kwargs.get("root_aliases", []) - - if not isinstance(self.root_aliases, (list, tuple)): - raise TypeError("Root aliases of a command must be a list or a tuple of strings.") - - -class _Group(commands.Group, _Command): - """ - A :obj:`disnake.ext.commands.Group` subclass which supports root aliases. - - A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as - top-level groups rather than being aliases of the command's group. It's stored as an attribute - also named ``root_aliases``. - """ - - -def _patch_typing() -> None: - """ - Sometimes Discord turns off typing events by throwing 403s. - - Handle those issues by patching disnake's internal ``send_typing`` method so it ignores 403s in general. - """ - log.debug("Patching send_typing, which should fix things breaking when Discord disables typing events. Stay safe!") - - original = http.HTTPClient.send_typing - last_403 = None - - async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001 - nonlocal last_403 - if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5): - log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.") - return - try: - await original(self, channel_id) - except Forbidden: - last_403 = datetime.utcnow() - log.warning("Got a 403 from typing event!") - - http.HTTPClient.send_typing = honeybadger_type - - -def apply_monkey_patches() -> None: - """ - Applies all common monkey patches for our bots. - - Patches :obj:`disnake.ext.commands.Command` and :obj:`disnake.ext.commands.Group` to support root aliases. - A ``root_aliases`` keyword argument is added to these two objects, which is a sequence of alias names - that will act as top-level groups rather than being aliases of the command's group. - - It's stored as an attribute also named ``root_aliases`` - - Patches disnake's internal ``send_typing`` method so that it ignores 403 errors from Discord. - When under heavy load Discord has added a CloudFlare worker to this route, which causes 403 errors to be thrown. - """ - commands.command = partial(commands.command, cls=_Command) - commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=_Command) - - commands.group = partial(commands.group, cls=_Group) - commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=_Group) - _patch_typing() -- cgit v1.2.3 From dcc78f1050be9d181500a3f727c2f4b7d087d451 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 3 Mar 2022 19:31:01 +0000 Subject: Don't break if functions are directly in module init files --- docs/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/utils.py b/docs/utils.py index 76b3e098..9116c130 100644 --- a/docs/utils.py +++ b/docs/utils.py @@ -105,7 +105,10 @@ def __get_included() -> set[str]: """Get a list of files that should be included in the final build.""" def get_all_from_module(module_name: str) -> set[str]: - module = importlib.import_module(module_name) + try: + module = importlib.import_module(module_name) + except ModuleNotFoundError: + return {} _modules = {module.__name__ + ".rst"} if hasattr(module, "__all__"): -- cgit v1.2.3 From 19c8ddd859d0d60038891e2e4c0717c585967198 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 3 Mar 2022 19:41:35 +0000 Subject: Document breaking monkey patch change --- CHANGELOG.md | 5 ++++- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67aca72b..09287fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog +## 3.0.0 3rd March 2021 + - Breaking: Move `apply_monkey_patches()` directly to `botcore.utils` namespace + ## 2.1.0 24th February 2022 - Feature: Port the Site API wrapper from the bot repo. ## 2.0.0 22nd February 2022 -- Breaking: Moved regex to botcore.utils namespace +- Breaking: Moved regex to `botcore.utils` namespace - Feature: Migrate from discord.py 2.0a0 to disnake. - Feature: Add common monkey patches. - Feature: Port many common utilities from our bots diff --git a/pyproject.toml b/pyproject.toml index c003cfc0..188edb8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bot-core" -version = "2.1.0" +version = "3.0.0" description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community." authors = ["Python Discord "] license = "MIT" -- cgit v1.2.3