Repository: hikari-py/hikari
Pull request #726 (2021-08-10 18:39:31)

Fix dm_channel_id cache component check
### Summary
<!-- Small summary of the merge request -->

### 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)
-->



Patch:
diff --git a/README.md b/README.md
index 0f686ee5..0959289d 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ implementing your own command handler.
 Hikari does not include a command framework by default, so you will want to pick a third party library to do it:
 
 - [`lightbulb`](https://github.com/tandemdude/hikari-lightbulb) - a simple and easy to use command framework for Hikari.
+- [`tanjun`](https://github.com/FasterSpeeding/Tanjun) - a flexible command framework designed to extend Hikari.
 
 ---
 
diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py
index c37f187b..e01efd91 100644
--- a/hikari/api/event_factory.py
+++ b/hikari/api/event_factory.py
@@ -466,69 +466,6 @@ def deserialize_presence_update_event(
     # INTERACTION EVENTS #
     ######################
 
-    @abc.abstractmethod
-    def deserialize_command_create_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandCreateEvent:
-        """Parse a raw payload from Discord into a command create event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandCreateEvent
-            The parsed command create event object.
-        """
-
-    @abc.abstractmethod
-    def deserialize_command_update_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandUpdateEvent:
-        """Parse a raw payload from Discord into a command update event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandUpdateEvent
-            The parsed command update event object.
-        """
-
-    @abc.abstractmethod
-    def deserialize_command_delete_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandDeleteEvent:
-        """Parse a raw payload from Discord into a command delete event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandDeleteEvent
-            The parsed command delete event object.
-        """
-
     @abc.abstractmethod
     def deserialize_interaction_create_event(
         self,
diff --git a/hikari/config.py b/hikari/config.py
index 351c6d78..53c64f05 100644
--- a/hikari/config.py
+++ b/hikari/config.py
@@ -450,9 +450,17 @@ class CacheSettings:
     """
 
     max_messages: int = attr.field(default=300)
-    """The max number of messages to store in the cache at once.
+    """The maximum number of messages to store in the cache at once.
 
     This will have no effect if the messages cache is not enabled.
 
     Defaults to `300`.
     """
+
+    max_dm_channel_ids: int = attr.field(default=50)
+    """The maximum number of channel IDs to store in the cache at once.
+
+    This will have no effect if the channel IDs cache is not enabled.
+
+    Defaults to `50`.
+    """
diff --git a/hikari/events/interaction_events.py b/hikari/events/interaction_events.py
index 11d169a3..be84606d 100644
--- a/hikari/events/interaction_events.py
+++ b/hikari/events/interaction_events.py
@@ -24,14 +24,9 @@
 from __future__ import annotations
 
 __all__: typing.List[str] = [
-    "CommandEvent",
-    "CommandCreateEvent",
-    "CommandUpdateEvent",
-    "CommandDeleteEvent",
     "InteractionCreateEvent",
 ]
 
-import abc
 import typing
 
 import attr
@@ -40,83 +35,11 @@
 from hikari.internal import attr_extensions
 
 if typing.TYPE_CHECKING:
-    from hikari import commands
     from hikari import traits
     from hikari.api import shard as gateway_shard
     from hikari.interactions import base_interactions
 
 
-class CommandEvent(shard_events.ShardEvent, abc.ABC):
-    """Base class of events fired for changes to application commands."""
-
-    @property
-    def app(self) -> traits.RESTAware:
-        # <<inherited docstring from Event>>.
-        return self.command.app
-
-    @property
-    @abc.abstractmethod
-    def command(self) -> commands.Command:
-        """Object of the command this event is for.
-
-        Returns
-        -------
-        hikari.commands.Command
-            The command this event is for.
-        """
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandCreateEvent(CommandEvent):
-    """Event fired when a command is created relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandUpdateEvent(CommandEvent):
-    """Event fired when a command is updated relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandDeleteEvent(CommandEvent):
-    """Event fired when a command is deleted relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
 @attr_extensions.with_copy
 @attr.define(kw_only=True, weakref_slot=False)
 class InteractionCreateEvent(shard_events.ShardEvent):
diff --git a/hikari/impl/cache.py b/hikari/impl/cache.py
index cd9f598f..0b694d1b 100644
--- a/hikari/impl/cache.py
+++ b/hikari/impl/cache.py
@@ -82,10 +82,10 @@ class CacheImpl(cache.MutableCache):
         "_settings",
     )
 
-    _dm_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, snowflakes.Snowflake]
     # For the sake of keeping things clean, the annotations are being kept separate from the assignment here.
     _me: typing.Optional[users.OwnUser]
     _emoji_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, cache_utility.KnownCustomEmojiData]
+    _dm_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, snowflakes.Snowflake]
     _guild_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, channels.GuildChannel]
     _guild_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, cache_utility.GuildRecord]
     _invite_entries: collections.ExtendedMutableMapping[str, cache_utility.InviteData]
@@ -104,7 +104,6 @@ class CacheImpl(cache.MutableCache):
 
     def __init__(self, app: traits.RESTAware, settings: config.CacheSettings) -> None:
         self._app = app
-        self._me = None
         self._settings = settings
         self._create_cache()
 
@@ -113,7 +112,8 @@ def settings(self) -> config.CacheSettings:
         return self._settings
 
     def _create_cache(self) -> None:
-        self._dm_channel_entries = collections.FreezableDict()
+        self._me = None
+        self._dm_channel_entries = collections.LimitedCapacityCacheMap(limit=self._settings.max_dm_channel_ids)
         self._emoji_entries = collections.FreezableDict()
         self._guild_channel_entries = collections.FreezableDict()
         self._guild_entries = collections.FreezableDict()
@@ -142,21 +142,33 @@ def clear(self) -> None:
         self._create_cache()
 
     def clear_dm_channel_ids(self) -> cache.CacheView[snowflakes.Snowflake, snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return cache_utility.EmptyCacheView()
+
         result = self._dm_channel_entries
-        self._dm_channel_entries = collections.FreezableDict()
+        self._dm_channel_entries = collections.LimitedCapacityCacheMap(limit=self._settings.max_dm_channel_ids)
         return cache_utility.CacheMappingView(result)
 
     def delete_dm_channel_id(
         self, user: snowflakes.SnowflakeishOr[users.PartialUser], /
     ) -> typing.Optional[snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
         return self._dm_channel_entries.pop(snowflakes.Snowflake(user), None)
 
     def get_dm_channel_id(
         self, user: snowflakes.SnowflakeishOr[users.PartialUser], /
     ) -> typing.Optional[snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
         return self._dm_channel_entries.get(snowflakes.Snowflake(user))
 
     def get_dm_channel_ids_view(self) -> cache.CacheView[snowflakes.Snowflake, snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return cache_utility.EmptyCacheView()
+
         return cache_utility.CacheMappingView(self._dm_channel_entries.freeze())
 
     def set_dm_channel_id(
@@ -165,9 +177,10 @@ def set_dm_channel_id(
         channel: snowflakes.SnowflakeishOr[channels.PartialChannel],
         /,
     ) -> None:
-        user = snowflakes.Snowflake(user)
-        if self._is_cache_enabled_for(config.CacheComponents.EMOJIS) and user in self._user_entries:
-            self._dm_channel_entries[user] = snowflakes.Snowflake(channel)
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
+        self._dm_channel_entries[snowflakes.Snowflake(user)] = snowflakes.Snowflake(channel)
 
     def _build_emoji(
         self,
diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py
index 2d876e29..80a23b24 100644
--- a/hikari/impl/event_factory.py
+++ b/hikari/impl/event_factory.py
@@ -344,33 +344,6 @@ def deserialize_presence_update_event(
     # INTERACTION EVENTS #
     ######################
 
-    def deserialize_command_create_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandCreateEvent:
-        return interaction_events.CommandCreateEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
-    def deserialize_command_update_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandUpdateEvent:
-        return interaction_events.CommandUpdateEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
-    def deserialize_command_delete_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandDeleteEvent:
-        return interaction_events.CommandDeleteEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
     def deserialize_interaction_create_event(
         self,
         shard: gateway_shard.GatewayShard,
diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py
index ef10e8af..8c36fb5b 100644
--- a/hikari/impl/event_manager.py
+++ b/hikari/impl/event_manager.py
@@ -489,24 +489,6 @@ async def on_webhooks_update(self, shard: gateway_shard.GatewayShard, payload: d
         """See https://discord.com/developers/docs/topics/gateway#webhooks-update for more info."""
         await self.dispatch(self._event_factory.deserialize_webhook_update_event(shard, payload))
 
-    async def on_application_command_create(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-create for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_create_event(shard, payload))
-
-    async def on_application_command_update(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-update for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_update_event(shard, payload))
-
-    async def on_application_command_delete(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-delete for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_delete_event(shard, payload))
-
     async def on_interaction_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None:
         """See https://discord.com/developers/docs/topics/gateway#interaction-create for more info."""
         await self.dispatch(self._event_factory.deserialize_interaction_create_event(shard, payload))


Test patch:
diff --git a/tests/hikari/events/test_interaction_events.py b/tests/hikari/events/test_interaction_events.py
index 379dc0b0..c32b33db 100644
--- a/tests/hikari/events/test_interaction_events.py
+++ b/tests/hikari/events/test_interaction_events.py
@@ -23,14 +23,6 @@
 import mock
 
 from hikari.events import interaction_events
-from tests.hikari import hikari_test_helpers
-
-
-class TestCommandEvent:
-    def test_app_property(self):
-        mock_event = hikari_test_helpers.mock_class_namespace(interaction_events.CommandEvent, command=mock.Mock())()
-
-        assert mock_event.app is mock_event.command.app
 
 
 class TestInteractionCreateEvent:
diff --git a/tests/hikari/impl/test_cache.py b/tests/hikari/impl/test_cache.py
index 8a252338..ae35630e 100644
--- a/tests/hikari/impl/test_cache.py
+++ b/tests/hikari/impl/test_cache.py
@@ -129,18 +129,6 @@ def test_set_dm_channel_id(self, cache_impl):
 
         assert cache_impl._dm_channel_entries == {43123123: 12222}
 
-    def test_set_dm_channel_id_when_user_not_known(self, cache_impl):
-        cache_impl.set_dm_channel_id(StubModel(43123123), StubModel(12222))
-
-        assert cache_impl._dm_channel_entries == {}
-
-    def test_set_dm_channel_id_when_not_enabled(self, cache_impl):
-        cache_impl._user_entries = collections.FreezableDict({43123123: object()})
-        cache_impl._settings.components = 0
-        cache_impl.set_dm_channel_id(StubModel(43123123), StubModel(12222))
-
-        assert cache_impl._dm_channel_entries == {}
-
     def test__build_emoji(self, cache_impl):
         mock_user = mock.MagicMock(users.User)
         emoji_data = cache_utilities.KnownCustomEmojiData(
@@ -2666,6 +2654,11 @@ def test_update_message_for_unknown_partial_message(self, cache_impl):
     @pytest.mark.parametrize(
         ("name", "component", "expected"),
         [
+            ("clear_dm_channel_ids", config.CacheComponents.DM_CHANNEL_IDS, cache_utilities.EmptyCacheView()),
+            ("delete_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
+            ("get_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
+            ("get_dm_channel_ids_view", config.CacheComponents.DM_CHANNEL_IDS, cache_utilities.EmptyCacheView()),
+            ("set_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
             ("clear_emojis", config.CacheComponents.EMOJIS, cache_utilities.EmptyCacheView()),
             ("clear_emojis_for_guild", config.CacheComponents.EMOJIS, cache_utilities.EmptyCacheView()),
             ("clear_guild_channels", config.CacheComponents.GUILD_CHANNELS, cache_utilities.EmptyCacheView()),
diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py
index 6389c39a..a6727b53 100644
--- a/tests/hikari/impl/test_event_factory.py
+++ b/tests/hikari/impl/test_event_factory.py
@@ -449,36 +449,6 @@ def test_deserialize_presence_update_event_with_partial_user_object(self, event_
     # INTERACTION EVENTS #
     ######################
 
-    def test_deserialize_command_create_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "123"}
-
-        result = event_factory.deserialize_command_create_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandCreateEvent)
-
-    def test_deserialize_command_update_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "12344"}
-
-        result = event_factory.deserialize_command_update_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandUpdateEvent)
-
-    def test_deserialize_command_delete_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "1561232344"}
-
-        result = event_factory.deserialize_command_delete_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandDeleteEvent)
-
     def test_deserialize_interaction_create_event(self, event_factory, mock_app, mock_shard):
         payload = {"id": "1561232344"}
 
diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py
index 62374556..8ec18f8d 100644
--- a/tests/hikari/impl/test_event_manager.py
+++ b/tests/hikari/impl/test_event_manager.py
@@ -1045,33 +1045,6 @@ async def test_on_webhooks_update(self, event_manager, shard, event_factory):
         event_factory.deserialize_webhook_update_event.assert_called_once_with(shard, payload)
         event_manager.dispatch.assert_awaited_once_with(event)
 
-    @pytest.mark.asyncio()
-    async def test_on_application_command_create(self, event_manager, shard, event_factory):
-        payload = {"id": "4544333334dd44"}
-
-        await event_manager.on_application_command_create(shard, payload)
-
-        event_factory.deserialize_command_create_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_create_event.return_value)
-
-    @pytest.mark.asyncio()
-    async def test_on_application_command_update(self, event_manager, shard, event_factory):
-        payload = {"id": "454433333444"}
-
-        await event_manager.on_application_command_update(shard, payload)
-
-        event_factory.deserialize_command_update_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_update_event.return_value)
-
-    @pytest.mark.asyncio()
-    async def test_on_application_command_delete(self, event_manager, shard, event_factory):
-        payload = {"id": "4544444"}
-
-        await event_manager.on_application_command_delete(shard, payload)
-
-        event_factory.deserialize_command_delete_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_delete_event.return_value)
-
     @pytest.mark.asyncio()
     async def test_on_interaction_create(self, event_manager, shard, event_factory):
         payload = {"id": "123"}