Repository: hikari-py/hikari
Pull request #754 (2021-08-28 23:10:46)

Add sticker methods to PartialGuild
### Summary
<!-- Small summary of the merge request -->
This pull request adds a few methods onto `hikari.guilds.PartialGuild`.
 - fetch_sticker
 - fetch_stickers
 - create_sticker
 - edit_sticker
 - delete_sticker

In addition, there were some mistakes in `hikari.api.rest` docstrings for the sticker methods which have been corrected.

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->
Related to #400 


davfsa: Before this is merged, could you rebase to master and add the change to the CHANGELOG.md so we can kick it off?
Jonxslays: rebased, and changelog updated.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2ccd9da6..27d14feb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,8 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-20.04, macos-10.15, windows-2019]
-        python-version: [3.8, 3.9, 3.10-dev]
+        # CPython 3.10-dev temporarily removed due to https://github.com/python/typing/issues/865
+        python-version: [3.8, 3.9]
 
     runs-on: ${{ matrix.os }}
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3102b0b..bb66404d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,3 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 *This file was added at the beginning of the development of version 2.0.0.dev102, so nothing before that is documented.*
 
 ## [Unreleased]
+### Added
+ - Sticker methods to PartialGuild.
+
+### Changed
+ - Fix errors in rest sticker method docstrings.
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index e9d288f6..90186a9a 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -2182,7 +2182,8 @@ async def delete_webhook(
     @abc.abstractmethod
     async def execute_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
         *,
@@ -2206,7 +2207,7 @@ async def execute_webhook(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snoflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2352,7 +2353,8 @@ async def execute_webhook(
     @abc.abstractmethod
     async def fetch_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
     ) -> messages_.Message:
@@ -2360,7 +2362,7 @@ async def fetch_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2398,7 +2400,8 @@ async def fetch_webhook_message(
     @abc.abstractmethod
     async def edit_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
         content: undefined.UndefinedNoneOr[typing.Any] = undefined.UNDEFINED,
@@ -2420,7 +2423,7 @@ async def edit_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2559,7 +2562,8 @@ async def edit_webhook_message(
     @abc.abstractmethod
     async def delete_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
     ) -> None:
@@ -2567,7 +2571,7 @@ async def delete_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -3706,7 +3710,8 @@ async def fetch_sticker(
         Parameters
         ----------
         sticker : snowflakes.SnowflakeishOr[stickers.PartialSticker]
-            The sticker to fetch.
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
 
         Returns
         -------
@@ -3743,7 +3748,8 @@ async def fetch_guild_stickers(
         Parameters
         ----------
         guild : snowflakes.SnowflakeishOr[stickers.PartialGuild]
-            The guild to request stickers for.
+            The guild to request stickers for. This can be a guild object or the
+            ID of an existing guild.
 
         Returns
         -------
@@ -3784,9 +3790,11 @@ async def fetch_guild_sticker(
         Parameters
         ----------
         guild : snowflakes.SnowflakeishOr[stickers.PartialGuild]
-            The guild the sticker is in.
+            The guild the sticker is in. This can be a guild object or the
+            ID of an existing guild.
         sticker : snowflakes.SnowflakeishOr[stickers.PartialSticker]
-            The sticker to fetch.
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
 
         Returns
         -------
@@ -3901,7 +3909,7 @@ async def edit_sticker(
         Parameters
         ----------
         guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
-            The guild to edit the emoji on. This can be a guild object or the
+            The guild to edit the sticker on. This can be a guild object or the
             ID of an existing guild.
         sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
             The sticker to edit. This can be a sticker object or the ID of an
@@ -3964,7 +3972,7 @@ async def delete_sticker(
         guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
             The guild to delete the sticker on. This can be a guild object or
             the ID of an existing guild.
-        sticker : hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
             The sticker to delete. This can be a sticker object or the ID
             of an existing sticker.
 
diff --git a/hikari/guilds.py b/hikari/guilds.py
index fd76c184..47fc6a7b 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -60,6 +60,7 @@
 
 from hikari import channels as channels_
 from hikari import snowflakes
+from hikari import stickers
 from hikari import traits
 from hikari import undefined
 from hikari import urls
@@ -1632,6 +1633,247 @@ async def fetch_emoji(self, emoji: snowflakes.SnowflakeishOr[emojis_.CustomEmoji
         """
         return await self.app.rest.fetch_emoji(self.id, emoji)
 
+    async def fetch_stickers(self) -> typing.Sequence[stickers.GuildSticker]:
+        """Fetch the stickers of the guild.
+
+        Returns
+        -------
+        typing.Sequence[hikari.stickers.GuildSticker]
+            The requested stickers.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the server.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild_stickers(self.id)
+
+    async def fetch_sticker(self, sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker]) -> stickers.GuildSticker:
+        """Fetch a sticker from the guild.
+
+        Parameters
+        ----------
+        sticker : snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The requested sticker.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild_sticker(self.id, sticker)
+
+    async def create_sticker(
+        self,
+        name: str,
+        tag: str,
+        image: files.Resourceish,
+        *,
+        description: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> stickers.GuildSticker:
+        """Create a sticker in a guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The name for the sticker.
+        tag : builtins.str
+            The tag for the sticker.
+        image : hikari.files.Resourceish
+            The 320x320 image for the sticker. Maximum upload size is 500kb.
+            This can be a still or an animated PNG or a Lottie.
+
+            !!! note
+                Lottie support is only available for verified and partnered
+                servers.
+
+        Other Parameters
+        ----------------
+        description: hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the description of the sticker.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The created sticker.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value or
+            if there are no more spaces for the sticker in the guild.
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_sticker(self.id, name, tag, image, description=description, reason=reason)
+
+    async def edit_sticker(
+        self,
+        sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker],
+        *,
+        name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        description: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        tag: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> stickers.GuildSticker:
+        """Edit a sticker in a guild.
+
+        Parameters
+        ----------
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to edit. This can be a sticker object or the ID of an
+            existing sticker.
+
+        Other Parameters
+        ----------------
+        name : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new name for the sticker.
+        description : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new description for the sticker.
+        tag : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new sticker tag.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The edited sticker.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.edit_sticker(
+            self.id, sticker, name=name, description=description, tag=tag, reason=reason
+        )
+
+    async def delete_sticker(
+        self,
+        sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker],
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Delete a sticker in a guild.
+
+        Parameters
+        ----------
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to delete. This can be a sticker object or the ID
+            of an existing sticker.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.delete_sticker(self.id, sticker, reason=reason)
+
     async def create_category(
         self,
         name: str,
diff --git a/hikari/impl/cache.py b/hikari/impl/cache.py
index 0b694d1b..ae54b45e 100644
--- a/hikari/impl/cache.py
+++ b/hikari/impl/cache.py
@@ -132,7 +132,7 @@ def _is_cache_enabled_for(self, required_flag: config.CacheComponents) -> bool:
         return (self._settings.components & required_flag) == required_flag
 
     @staticmethod
-    def _increment_ref_count(obj: cache_utility.RefCell[_T], increment: int = 1) -> None:
+    def _increment_ref_count(obj: cache_utility.RefCell[typing.Any], increment: int = 1) -> None:
         obj.ref_count += increment
 
     def clear(self) -> None:
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 0d615dfe..71729571 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -1725,7 +1725,7 @@ async def delete_webhook(
 
     async def execute_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
         *,
@@ -1745,7 +1745,9 @@ async def execute_webhook(
         ] = undefined.UNDEFINED,
         flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
     ) -> messages_.Message:
-        route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=webhook, token=token)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=webhook_id, token=token)
 
         body = data_binding.JSONObjectBuilder()
         body.put("username", username)
@@ -1772,18 +1774,20 @@ async def execute_webhook(
 
     async def fetch_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
     ) -> messages_.Message:
-        route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
         response = await self._request(route, no_auth=True)
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_message(response)
 
     async def edit_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
         content: undefined.UndefinedNoneOr[typing.Any] = undefined.UNDEFINED,
@@ -1801,7 +1805,9 @@ async def edit_webhook_message(
             typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
         ] = undefined.UNDEFINED,
     ) -> messages_.Message:
-        route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
         body = data_binding.JSONObjectBuilder()
         return await self._edit_message(
             route,
@@ -1821,11 +1827,13 @@ async def edit_webhook_message(
 
     async def delete_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
     ) -> None:
-        route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
         await self._request(route, no_auth=True)
 
     async def fetch_gateway_url(self) -> str:


Test patch:
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index c71715f7..827abba6 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -42,6 +42,7 @@
 from hikari import undefined
 from hikari import urls
 from hikari import users
+from hikari import webhooks
 from hikari.api import rest as rest_api
 from hikari.impl import buckets
 from hikari.impl import entity_factory
@@ -1989,19 +1990,20 @@ async def test_execute_webhook(self, rest_client):
 
     async def test_execute_webhook_when_attachment_and_attachments_given(self, rest_client):
         with pytest.raises(ValueError, match="You may only specify one of 'attachment' or 'attachments', not both"):
-            await rest_client.execute_webhook(StubModel(123), "token", attachment=object(), attachments=object())
+            await rest_client.execute_webhook(123, "token", attachment=object(), attachments=object())
 
     async def test_execute_webhook_when_embed_and_embeds_given(self, rest_client):
         with pytest.raises(ValueError, match="You may only specify one of 'embed' or 'embeds', not both"):
-            await rest_client.execute_webhook(StubModel(123), "token", embed=object(), embeds=object())
+            await rest_client.execute_webhook(123, "token", embed=object(), embeds=object())
 
-    async def test_fetch_webhook_message(self, rest_client):
+    @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=432), 432])
+    async def test_fetch_webhook_message(self, rest_client, webhook):
         message_obj = mock.Mock()
-        expected_route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=123, token="hi, im a token", message=456)
+        expected_route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=432, token="hi, im a token", message=456)
         rest_client._request = mock.AsyncMock(return_value={"id": "456"})
         rest_client._entity_factory.deserialize_message = mock.Mock(return_value=message_obj)
 
-        assert await rest_client.fetch_webhook_message(StubModel(123), "hi, im a token", StubModel(456)) is message_obj
+        assert await rest_client.fetch_webhook_message(webhook, "hi, im a token", StubModel(456)) is message_obj
         rest_client._request.assert_awaited_once_with(expected_route, no_auth=True)
         rest_client._entity_factory.deserialize_message.assert_called_once_with({"id": "456"})
 
@@ -2009,11 +2011,12 @@ async def test_fetch_webhook_message(self, rest_client):
     async def test_edit_webhook_message(self, rest_client):
         ...  # TODO: Implement
 
-    async def test_delete_webhook_message(self, rest_client):
+    @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=123), 123])
+    async def test_delete_webhook_message(self, rest_client, webhook):
         expected_route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=123, token="token", message=456)
         rest_client._request = mock.AsyncMock()
 
-        await rest_client.delete_webhook_message(StubModel(123), "token", StubModel(456))
+        await rest_client.delete_webhook_message(webhook, "token", StubModel(456))
         rest_client._request.assert_awaited_once_with(expected_route, no_auth=True)
 
     async def test_fetch_gateway_url(self, rest_client):
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index e921371c..28d697fd 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -529,6 +529,73 @@ async def test_fetch_emoji(self, model):
         model.app.rest.fetch_emoji.assert_awaited_once_with(model.id, 349)
         assert emoji is model.app.rest.fetch_emoji.return_value
 
+    @pytest.mark.asyncio()
+    async def test_fetch_stickers(self, model):
+        model.app.rest.fetch_guild_stickers = mock.AsyncMock()
+
+        stickers = await model.fetch_stickers()
+
+        model.app.rest.fetch_guild_stickers.assert_awaited_once_with(model.id)
+        assert stickers is model.app.rest.fetch_guild_stickers.return_value
+
+    @pytest.mark.asyncio()
+    async def test_fetch_sticker(self, model):
+        model.app.rest.fetch_guild_sticker = mock.AsyncMock()
+
+        sticker = await model.fetch_sticker(6969)
+
+        model.app.rest.fetch_guild_sticker.assert_awaited_once_with(model.id, 6969)
+        assert sticker is model.app.rest.fetch_guild_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_sticker(self, model):
+        model.app.rest.create_sticker = mock.AsyncMock()
+        file = object()
+
+        sticker = await model.create_sticker("NewSticker", "funny", file)
+
+        model.app.rest.create_sticker.assert_awaited_once_with(
+            90210,
+            "NewSticker",
+            "funny",
+            file,
+            description=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.create_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_edit_sticker(self, model):
+        model.app.rest.edit_sticker = mock.AsyncMock()
+
+        sticker = await model.edit_sticker(4567, name="Brilliant", tag="parmesan", description="amazing")
+
+        model.app.rest.edit_sticker.assert_awaited_once_with(
+            90210,
+            4567,
+            name="Brilliant",
+            tag="parmesan",
+            description="amazing",
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.edit_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_delete_sticker(self, model):
+        model.app.rest.delete_sticker = mock.AsyncMock()
+
+        sticker = await model.delete_sticker(951)
+
+        model.app.rest.delete_sticker.assert_awaited_once_with(
+            90210,
+            951,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.delete_sticker.return_value
+
     @pytest.mark.asyncio()
     async def test_create_category(self, model):
         model.app.rest.create_guild_category = mock.AsyncMock()