Repository: hikari-py/hikari
Pull request #1581 (2023-04-03 14:59:22)

Fix `messages` argument typing for `delete_messages` API

FasterSpeeding: You need to update https://github.com/hikari-py/hikari/blob/ef7bdc767e1db2e078f54455d18e30aac74563dc/hikari/channels.py#L778
davfsa: > You need to update
> 
> https://github.com/hikari-py/hikari/blob/ef7bdc767e1db2e078f54455d18e30aac74563dc/hikari/channels.py#L778

Nice spot, thanks!

Sorted out :)

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 57075bf8..67775e08 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,7 +86,7 @@ jobs:
           coverage report
 
       - name: Upload coverage to codeclimate
-        uses: paambaati/codeclimate-action@v3.2.0
+        uses: paambaati/codeclimate-action@v4.0.0
         env:
           CC_TEST_REPORTER_ID: d40e64ea0ff74713f79365fea4378ab51a2141ad4fcf0fb118496bbf560d4192
         with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c115f6d0..71cf5088 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,7 +32,7 @@ jobs:
           python-version: 3.8
 
       - name: Temporarily disable "include administrators" branch protection
-        uses: benjefferies/branch-protection-bot@1.0.7
+        uses: benjefferies/branch-protection-bot@1.0.8
         with:
           access_token: ${{ steps.generate_token.outputs.token }}
           enforce_admins: false
@@ -46,7 +46,7 @@ jobs:
         run: bash scripts/ci/release.sh
 
       - name: Re-enable "include administrators" branch protection
-        uses: benjefferies/branch-protection-bot@1.0.7
+        uses: benjefferies/branch-protection-bot@1.0.8
         if: always()
         with:
           access_token: ${{ steps.generate_token.outputs.token }}
diff --git a/changes/1581.bugfix.md b/changes/1581.bugfix.md
new file mode 100644
index 00000000..d9006db3
--- /dev/null
+++ b/changes/1581.bugfix.md
@@ -0,0 +1,1 @@
+Fix `messages` argument typing for `RESTClient.delete_messages`.
diff --git a/changes/1585.bugfix.md b/changes/1585.bugfix.md
new file mode 100644
index 00000000..428176b3
--- /dev/null
+++ b/changes/1585.bugfix.md
@@ -0,0 +1,2 @@
+Default `HTTPSettings.enable_cleanup_closed` to `False`.
+- CPython3.11 changes around SSLProto have made this quite unstable and prone to errors when dealing with unclosed TLS transports, which ends up in aiohttp calling close and abort twice.
diff --git a/changes/1608.bugfix.md b/changes/1608.bugfix.md
new file mode 100644
index 00000000..11faecb4
--- /dev/null
+++ b/changes/1608.bugfix.md
@@ -0,0 +1,1 @@
+`Guild.get_channel`, `Guild.get_emoji`, `Guild.get_sticker` and `Guild.get_role` now only return entries from the relevant guild.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 749e4f01..884be2e7 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,1 +1,1 @@
-nox==2022.11.21
+nox==2023.4.22
diff --git a/dev-requirements/coverage.txt b/dev-requirements/coverage.txt
index ab24e8d1..d92b75a6 100644
--- a/dev-requirements/coverage.txt
+++ b/dev-requirements/coverage.txt
@@ -1,1 +1,1 @@
-coverage[toml]==7.2.3
+coverage[toml]==7.2.5
diff --git a/dev-requirements/flake8.txt b/dev-requirements/flake8.txt
index c08ec430..b7a7a4d5 100644
--- a/dev-requirements/flake8.txt
+++ b/dev-requirements/flake8.txt
@@ -7,11 +7,11 @@ flake8-bandit~=4.1.1                    # runs bandit
 flake8-black==0.3.6                     # runs black
 flake8-builtins==2.1.0                  # builtin shadowing checks
 flake8-coding==1.3.2                    # coding magic-comment detection
-flake8-comprehensions==3.11.1           # comprehension checks
+flake8-comprehensions==3.12.0           # comprehension checks
 flake8-docstrings==1.7.0                # pydocstyle support
 flake8-executable==2.1.3                # shebangs
 flake8-fixme==1.1.1                     # "fix me" counter
-flake8-functions==0.0.7                 # function linting
+flake8-functions==0.0.8                 # function linting
 flake8-html==0.4.3                      # html output
 flake8-isort==6.0.0                     # runs isort
 flake8-mutable==1.2.0                   # mutable default argument detection
diff --git a/dev-requirements/pyright.txt b/dev-requirements/pyright.txt
index 6928ec7f..e84b01d3 100644
--- a/dev-requirements/pyright.txt
+++ b/dev-requirements/pyright.txt
@@ -1,1 +1,1 @@
-pyright==1.1.302
+pyright==1.1.306
diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt
index 6b35b0ee..3ae88af7 100644
--- a/dev-requirements/sphinx.txt
+++ b/dev-requirements/sphinx.txt
@@ -1,4 +1,4 @@
-sphinx==6.1.3
+sphinx==6.2.1
 
 # Theme
 furo==2023.3.27
@@ -11,6 +11,6 @@ numpydoc==1.5.0
 myst-parser==1.0.0
 
 # Misc extensions
-sphinxext.opengraph==0.8.1
-sphinx-copybutton==0.5.1
+sphinxext.opengraph==0.8.2
+sphinx-copybutton==0.5.2
 sphinxcontrib-towncrier==0.3.2a0
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index d276a988..26752978 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -1422,7 +1422,8 @@ async def delete_messages(
         channel: snowflakes.SnowflakeishOr[channels_.TextableChannel],
         messages: typing.Union[
             snowflakes.SnowflakeishOr[messages_.PartialMessage],
-            snowflakes.SnowflakeishIterable[messages_.PartialMessage],
+            typing.Iterable[snowflakes.SnowflakeishOr[messages_.PartialMessage]],
+            typing.AsyncIterable[snowflakes.SnowflakeishOr[messages_.PartialMessage]],
         ],
         /,
         *other_messages: snowflakes.SnowflakeishOr[messages_.PartialMessage],
@@ -1455,9 +1456,9 @@ async def delete_messages(
         channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel]
             The channel to bulk delete the messages in. This may be
             the object or the ID of an existing channel.
-        messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]]
+        messages
             Either the object/ID of an existing message to delete or an iterable
-            of the objects and/or IDs of existing messages to delete.
+            (sync or async) of the objects and/or IDs of existing messages to delete.
 
         Other Parameters
         ----------------
@@ -1471,7 +1472,7 @@ async def delete_messages(
             messages that were not removed. The
             `BaseException.__cause__` of the exception will be the
             original error that terminated this process.
-        """  # noqa: E501 - Line too long
+        """
 
     @abc.abstractmethod
     async def add_reaction(
diff --git a/hikari/channels.py b/hikari/channels.py
index d1271d7d..75d3cde6 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -79,7 +79,7 @@
     from hikari import files
     from hikari import guilds
     from hikari import iterators
-    from hikari import messages
+    from hikari import messages as messages_
     from hikari import stickers as stickers_
     from hikari import users
     from hikari import voices
@@ -409,7 +409,7 @@ def fetch_history(
         before: undefined.UndefinedOr[snowflakes.SearchableSnowflakeishOr[snowflakes.Unique]] = undefined.UNDEFINED,
         after: undefined.UndefinedOr[snowflakes.SearchableSnowflakeishOr[snowflakes.Unique]] = undefined.UNDEFINED,
         around: undefined.UndefinedOr[snowflakes.SearchableSnowflakeishOr[snowflakes.Unique]] = undefined.UNDEFINED,
-    ) -> iterators.LazyIterator[messages.Message]:
+    ) -> iterators.LazyIterator[messages_.Message]:
         """Browse the message history for a given text channel.
 
         .. note::
@@ -457,7 +457,7 @@ def fetch_history(
         """
         return self.app.rest.fetch_messages(self.id, before=before, after=after, around=around)
 
-    async def fetch_message(self, message: snowflakes.SnowflakeishOr[messages.PartialMessage]) -> messages.Message:
+    async def fetch_message(self, message: snowflakes.SnowflakeishOr[messages_.PartialMessage]) -> messages_.Message:
         """Fetch a specific message in the given text channel.
 
         Parameters
@@ -503,7 +503,7 @@ async def send(
             snowflakes.SnowflakeishSequence[stickers_.PartialSticker]
         ] = undefined.UNDEFINED,
         tts: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
-        reply: undefined.UndefinedOr[snowflakes.SnowflakeishOr[messages.PartialMessage]] = undefined.UNDEFINED,
+        reply: undefined.UndefinedOr[snowflakes.SnowflakeishOr[messages_.PartialMessage]] = undefined.UNDEFINED,
         reply_must_exist: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
         mentions_everyone: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
         mentions_reply: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
@@ -513,8 +513,8 @@ async def send(
         role_mentions: undefined.UndefinedOr[
             typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
         ] = undefined.UNDEFINED,
-        flags: typing.Union[undefined.UndefinedType, int, messages.MessageFlag] = undefined.UNDEFINED,
-    ) -> messages.Message:
+        flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
+    ) -> messages_.Message:
         """Create a message in this channel.
 
         Parameters
@@ -699,7 +699,7 @@ def trigger_typing(self) -> special_endpoints.TypingIndicator:
         """
         return self.app.rest.trigger_typing(self.id)
 
-    async def fetch_pins(self) -> typing.Sequence[messages.Message]:
+    async def fetch_pins(self) -> typing.Sequence[messages_.Message]:
         """Fetch the pinned messages in this text channel.
 
         Returns
@@ -723,7 +723,7 @@ async def fetch_pins(self) -> typing.Sequence[messages.Message]:
         """
         return await self.app.rest.fetch_pins(self.id)
 
-    async def pin_message(self, message: snowflakes.SnowflakeishOr[messages.PartialMessage]) -> None:
+    async def pin_message(self, message: snowflakes.SnowflakeishOr[messages_.PartialMessage]) -> None:
         """Pin an existing message in the text channel.
 
         Parameters
@@ -749,7 +749,7 @@ async def pin_message(self, message: snowflakes.SnowflakeishOr[messages.PartialM
         """
         return await self.app.rest.pin_message(self.id, message)
 
-    async def unpin_message(self, message: snowflakes.SnowflakeishOr[messages.PartialMessage]) -> None:
+    async def unpin_message(self, message: snowflakes.SnowflakeishOr[messages_.PartialMessage]) -> None:
         """Unpin a given message from the text channel.
 
         Parameters
@@ -778,10 +778,12 @@ async def unpin_message(self, message: snowflakes.SnowflakeishOr[messages.Partia
     async def delete_messages(
         self,
         messages: typing.Union[
-            snowflakes.SnowflakeishOr[messages.PartialMessage], snowflakes.SnowflakeishIterable[messages.PartialMessage]
+            snowflakes.SnowflakeishOr[messages_.PartialMessage],
+            typing.Iterable[snowflakes.SnowflakeishOr[messages_.PartialMessage]],
+            typing.AsyncIterable[snowflakes.SnowflakeishOr[messages_.PartialMessage]],
         ],
         /,
-        *other_messages: snowflakes.SnowflakeishOr[messages.PartialMessage],
+        *other_messages: snowflakes.SnowflakeishOr[messages_.PartialMessage],
     ) -> None:
         """Bulk-delete messages from the channel.
 
@@ -808,9 +810,9 @@ async def delete_messages(
 
         Parameters
         ----------
-        messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]]
+        messages
             Either the object/ID of an existing message to delete or an iterable
-            of the objects and/or IDs of existing messages to delete.
+            (sync or async) of the objects and/or IDs of existing messages to delete.
 
         Other Parameters
         ----------------
@@ -824,7 +826,7 @@ async def delete_messages(
             messages that were not removed. The
             `BaseException.__cause__` of the exception will be the
             original error that terminated this process.
-        """  # noqa: E501 - Line too long
+        """
         return await self.app.rest.delete_messages(self.id, messages, *other_messages)
 
 
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 0ecd4e3e..09013e8e 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -2898,7 +2898,11 @@ def get_channel(
         if not isinstance(self.app, traits.CacheAware):
             return None
 
-        return self.app.cache.get_guild_channel(channel)
+        channel_ = self.app.cache.get_guild_channel(channel)
+        if channel_ and channel_.guild_id == self.id:
+            return channel_
+
+        return None
 
     def get_member(self, user: snowflakes.SnowflakeishOr[users.PartialUser]) -> typing.Optional[Member]:
         """Get a cached member that belongs to the guild by it's user ID or object.
@@ -2994,7 +2998,11 @@ def get_emoji(
         if not isinstance(self.app, traits.CacheAware):
             return None
 
-        return self.app.cache.get_emoji(emoji)
+        emoji_ = self.app.cache.get_emoji(emoji)
+        if emoji_ and emoji_.guild_id == self.id:
+            return emoji_
+
+        return None
 
     def get_sticker(
         self, sticker: snowflakes.SnowflakeishOr[stickers.GuildSticker]
@@ -3015,7 +3023,11 @@ def get_sticker(
         if not isinstance(self.app, traits.CacheAware):
             return None
 
-        return self.app.cache.get_sticker(sticker)
+        sticker_ = self.app.cache.get_sticker(sticker)
+        if sticker_ and sticker_.guild_id == self.id:
+            return sticker_
+
+        return None
 
     def get_role(self, role: snowflakes.SnowflakeishOr[PartialRole]) -> typing.Optional[Role]:
         """Get a cached role that belongs to the guild by it's ID or object.
@@ -3033,7 +3045,11 @@ def get_role(self, role: snowflakes.SnowflakeishOr[PartialRole]) -> typing.Optio
         if not isinstance(self.app, traits.CacheAware):
             return None
 
-        return self.app.cache.get_role(role)
+        role_ = self.app.cache.get_role(role)
+        if role_ and role_.guild_id == self.id:
+            return role_
+
+        return None
 
     async def fetch_owner(self) -> Member:
         """Fetch the owner of the guild.
diff --git a/hikari/impl/config.py b/hikari/impl/config.py
index 8b72be07..4f0916f4 100644
--- a/hikari/impl/config.py
+++ b/hikari/impl/config.py
@@ -200,12 +200,12 @@ def _(self, attrsib: attrs.Attribute[typing.Optional[float]], value: typing.Any)
 class HTTPSettings(config.HTTPSettings):
     """Settings to control HTTP clients."""
 
-    enable_cleanup_closed: bool = attrs.field(default=True, validator=attrs.validators.instance_of(bool))
+    enable_cleanup_closed: bool = attrs.field(default=False, validator=attrs.validators.instance_of(bool))
     """Toggle whether to clean up closed transports.
 
-    This defaults to `True` to combat various protocol and asyncio
-    issues present when using Microsoft Windows. If you are sure you know
-    what you are doing, you may instead set this to `False` to disable this
+    This defaults to `False` to combat various protocol and asyncio
+    issues present. If you are sure you know  what you are doing,
+    you may instead set this to `True` to enable this
     behavior internally.
     """
 
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index 131816d3..08738bc1 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -334,7 +334,8 @@ def warn_if_not_optimized(suppress: bool) -> None:
     if __debug__ and not suppress:
         _LOGGER.warning(
             "You are running on optimization level 0 (no optimizations), which may slow down your application. "
-            "For production, consider using at least level 1 optimization by passing `-O` to the python call."
+            "For production, consider using at least level 1 optimization by passing `-O` to the python "
+            "interpreter call"
         )
 
 
diff --git a/requirements.txt b/requirements.txt
index 5d1adc4c..1526161d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
 aiohttp~=3.8
-attrs~=22.2
+attrs~=23.1
 colorlog~=6.7
 multidict~=6.0


Test patch:
diff --git a/dev-requirements/pytest.txt b/dev-requirements/pytest.txt
index 140ef27e..07c610dc 100644
--- a/dev-requirements/pytest.txt
+++ b/dev-requirements/pytest.txt
@@ -1,7 +1,7 @@
 # mock in the stdlib changes between versions of Python, so are not consistent in behaviour like the backport is
-mock==5.0.1
+mock==5.0.2
 
-pytest==7.2.2
+pytest==7.3.1
 pytest-asyncio==0.21.0
 pytest-cov==4.0.0
 pytest-randomly==3.12.0
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index 2c4d58f5..5142fa56 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -1021,9 +1021,17 @@ def test_emojis_when_no_cache_trait(self, model):
         assert model.get_emojis() == {}
 
     def test_get_sticker(self, model):
+        model.app.cache.get_sticker.return_value.guild_id = model.id
         assert model.get_sticker(456) is model.app.cache.get_sticker.return_value
         model.app.cache.get_sticker.assert_called_once_with(456)
 
+    def test_get_sticker_when_not_from_guild(self, model):
+        model.app.cache.get_sticker.return_value.guild_id = 546123123433
+
+        assert model.get_sticker(456) is None
+
+        model.app.cache.get_sticker.assert_called_once_with(456)
+
     def test_get_sticker_when_no_cache_trait(self, model):
         model.app = object()
         assert model.get_sticker(1234) is None
@@ -1045,17 +1053,33 @@ def test_get_roles_when_no_cache_trait(self, model):
         assert model.get_roles() == {}
 
     def test_get_emoji(self, model):
+        model.app.cache.get_emoji.return_value.guild_id = model.id
         assert model.get_emoji(456) is model.app.cache.get_emoji.return_value
         model.app.cache.get_emoji.assert_called_once_with(456)
 
+    def test_get_emoji_when_not_from_guild(self, model):
+        model.app.cache.get_emoji.return_value.guild_id = 1233212
+
+        assert model.get_emoji(456) is None
+
+        model.app.cache.get_emoji.assert_called_once_with(456)
+
     def test_get_emoji_when_no_cache_trait(self, model):
         model.app = object()
         assert model.get_emoji(456) is None
 
     def test_get_role(self, model):
+        model.app.cache.get_role.return_value.guild_id = model.id
         assert model.get_role(456) is model.app.cache.get_role.return_value
         model.app.cache.get_role.assert_called_once_with(456)
 
+    def test_get_role_when_not_from_guild(self, model):
+        model.app.cache.get_role.return_value.guild_id = 7623123321123
+
+        assert model.get_role(456) is None
+
+        model.app.cache.get_role.assert_called_once_with(456)
+
     def test_get_role_when_no_cache_trait(self, model):
         model.app = object()
         assert model.get_role(456) is None
@@ -1226,9 +1250,17 @@ async def test_fetch_afk_channel_when_None(self, model):
         assert await model.fetch_afk_channel() is None
 
     def test_get_channel(self, model):
+        model.app.cache.get_guild_channel.return_value.guild_id = model.id
         assert model.get_channel(456) is model.app.cache.get_guild_channel.return_value
         model.app.cache.get_guild_channel.assert_called_once_with(456)
 
+    def test_get_channel_when_not_from_guild(self, model):
+        model.app.cache.get_guild_channel.return_value.guild_id = 654523123
+
+        assert model.get_channel(456) is None
+
+        model.app.cache.get_guild_channel.assert_called_once_with(456)
+
     def test_get_channel_when_no_cache_trait(self, model):
         model.app = object()
         assert model.get_channel(456) is None