Repository: hikari-py/hikari
Pull request #1344 (2022-11-06 09:09:15)

Fix deprecation warnings in cpython3.11
### 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.


FasterSpeeding: This breaks tests on some runtime 
davfsa: @FasterSpeeding all fixed. Sorry :sweat_smile: 

Patch:
diff --git a/changes/1337.bugfix.md b/changes/1337.bugfix.md
new file mode 100644
index 00000000..c84a2e95
--- /dev/null
+++ b/changes/1337.bugfix.md
@@ -0,0 +1,1 @@
+The REST list methods (e.g. `fetch_channels`) no-longer raise `hikari.errors.UnrecognisedEntityError` when they encounter an unknown type.
diff --git a/changes/1344.bugfix.md b/changes/1344.bugfix.md
new file mode 100644
index 00000000..d4fc65c8
--- /dev/null
+++ b/changes/1344.bugfix.md
@@ -0,0 +1,1 @@
+Fix deprecation warnings in CPython3.11 in `hikari.internal.ux`.
diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py
index c758c064..361efbc3 100644
--- a/hikari/api/entity_factory.py
+++ b/hikari/api/entity_factory.py
@@ -1227,6 +1227,11 @@ def deserialize_command(
             If `guild_id` is left as `hikari.undefined.UNDEFINED` when
             `"guild_id"` is not present in the passed payload for the payload of
             the integration.
+
+        Raises
+        ------
+        hikari.errors.UnrecognisedEntityError
+            If the command type is unknown.
         """
 
     @abc.abstractmethod
@@ -1609,6 +1614,11 @@ def deserialize_scheduled_event(self, payload: data_binding.JSONObject) -> sched
         -------
         hikari.scheduled_events.ScheduledEvent
             The deserialized scheduled event object.
+
+        Raises
+        ------
+        hikari.errors.UnrecognisedEntityError
+            If the scheduled event type is unknown.
         """
 
     @abc.abstractmethod
@@ -1820,4 +1830,9 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model
         -------
         hikari.webhooks.PartialWebhook
             The deserialized webhook object.
+
+        Raises
+        ------
+        hikari.errors.UnrecognisedEntityError
+            If the channel type is unknown.
         """
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 8d31ceda..6e07f4bd 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -2560,15 +2560,7 @@ def deserialize_guild_sticker(self, payload: data_binding.JSONObject) -> sticker
     ##################
 
     def _deserialize_action_row(self, payload: data_binding.JSONObject) -> message_models.ActionRowComponent:
-        components: typing.List[message_models.PartialComponent] = []
-
-        for component_payload in payload["components"]:
-            try:
-                components.append(self._deserialize_component(component_payload))
-
-            except errors.UnrecognisedEntityError:
-                pass
-
+        components = data_binding.cast_variants_array(self._deserialize_component, payload["components"])
         return message_models.ActionRowComponent(
             type=message_models.ComponentType(payload["type"]), components=components
         )
@@ -2759,13 +2751,7 @@ def deserialize_partial_message(  # noqa CFQ001 - Function too long
 
         components: undefined.UndefinedOr[typing.List[message_models.PartialComponent]] = undefined.UNDEFINED
         if component_payloads := payload.get("components"):
-            components = []
-            for component_payload in component_payloads:
-                try:
-                    components.append(self._deserialize_component(component_payload))
-
-                except errors.UnrecognisedEntityError:
-                    pass
+            components = data_binding.cast_variants_array(self._deserialize_component, component_payloads)
 
         channel_mentions: undefined.UndefinedOr[
             typing.Dict[snowflakes.Snowflake, channel_models.PartialChannel]
@@ -2873,14 +2859,11 @@ def deserialize_message(
         if interaction_payload := payload.get("interaction"):
             interaction = self._deserialize_message_interaction(interaction_payload)
 
-        components: typing.List[message_models.PartialComponent] = []
         if component_payloads := payload.get("components"):
-            for component_payload in component_payloads:
-                try:
-                    components.append(self._deserialize_component(component_payload))
+            components = data_binding.cast_variants_array(self._deserialize_component, component_payloads)
 
-                except errors.UnrecognisedEntityError:
-                    pass
+        else:
+            components = []
 
         user_mentions = {u.id: u for u in map(self.deserialize_user, payload.get("mentions", ()))}
         role_mention_ids = [snowflakes.Snowflake(i) for i in payload.get("mention_roles", ())]
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index f9650892..85c843c5 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -1753,7 +1753,7 @@ async def fetch_channel_webhooks(
         route = routes.GET_CHANNEL_WEBHOOKS.compile(channel=channel)
         response = await self._request(route)
         assert isinstance(response, list)
-        return [self._entity_factory.deserialize_webhook(webhook_pl) for webhook_pl in response]
+        return data_binding.cast_variants_array(self._entity_factory.deserialize_webhook, response)
 
     async def fetch_guild_webhooks(
         self,
@@ -1762,7 +1762,7 @@ async def fetch_guild_webhooks(
         route = routes.GET_GUILD_WEBHOOKS.compile(guild=guild)
         response = await self._request(route)
         assert isinstance(response, list)
-        return [self._entity_factory.deserialize_webhook(webhook_payload) for webhook_payload in response]
+        return data_binding.cast_variants_array(self._entity_factory.deserialize_webhook, response)
 
     async def edit_webhook(
         self,
@@ -2585,9 +2585,9 @@ async def fetch_guild_channels(
         route = routes.GET_GUILD_CHANNELS.compile(guild=guild)
         response = await self._request(route)
         assert isinstance(response, list)
-        channel_sequence = [self._entity_factory.deserialize_channel(channel_payload) for channel_payload in response]
+        channels = data_binding.cast_variants_array(self._entity_factory.deserialize_channel, response)
         # Will always be guild channels unless Discord messes up severely on something!
-        return typing.cast("typing.Sequence[channels_.GuildChannel]", channel_sequence)
+        return typing.cast("typing.Sequence[channels_.GuildChannel]", channels)
 
     async def create_guild_text_channel(
         self,
@@ -3714,6 +3714,19 @@ async def fetch_application_command(
             response, guild_id=snowflakes.Snowflake(guild) if guild is not undefined.UNDEFINED else None
         )
 
+    def _deserialize_command_list(
+        self, command_payloads: data_binding.JSONArray, guild_id: typing.Optional[snowflakes.Snowflake]
+    ) -> typing.List[commands.PartialCommand]:
+        command_objs: typing.List[commands.PartialCommand] = []
+        for payload in command_payloads:
+            try:
+                command_objs.append(self._entity_factory.deserialize_command(payload, guild_id=guild_id))
+
+            except errors.UnrecognisedEntityError:
+                pass
+
+        return command_objs
+
     async def fetch_application_commands(
         self,
         application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
@@ -3731,7 +3744,7 @@ async def fetch_application_commands(
         response = await self._request(route, query=query)
         assert isinstance(response, list)
         guild_id = snowflakes.Snowflake(guild) if guild is not undefined.UNDEFINED else None
-        return [self._entity_factory.deserialize_command(command, guild_id=guild_id) for command in response]
+        return self._deserialize_command_list(response, guild_id)
 
     async def _create_application_command(
         self,
@@ -3854,7 +3867,7 @@ async def set_application_commands(
         response = await self._request(route, json=[command.build(self._entity_factory) for command in commands])
         assert isinstance(response, list)
         guild_id = snowflakes.Snowflake(guild) if guild is not undefined.UNDEFINED else None
-        return [self._entity_factory.deserialize_command(payload, guild_id=guild_id) for payload in response]
+        return self._deserialize_command_list(response, guild_id)
 
     async def edit_application_command(
         self,
@@ -4125,7 +4138,7 @@ async def fetch_scheduled_events(
         response = await self._request(route, query=query)
 
         assert isinstance(response, list)
-        return [self._entity_factory.deserialize_scheduled_event(event) for event in response]
+        return data_binding.cast_variants_array(self._entity_factory.deserialize_scheduled_event, response)
 
     async def _create_or_edit_scheduled_stage(
         self,
diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py
index 90017e8d..40b8613e 100644
--- a/hikari/internal/data_binding.py
+++ b/hikari/internal/data_binding.py
@@ -41,6 +41,7 @@
 import aiohttp
 import multidict
 
+from hikari import errors
 from hikari import files
 from hikari import snowflakes
 from hikari import undefined
@@ -50,6 +51,7 @@
     import contextlib
 
     T_co = typing.TypeVar("T_co", covariant=True)
+    T = typing.TypeVar("T")
 
 Headers = typing.Mapping[str, str]
 """Type hint for HTTP headers."""
@@ -392,3 +394,33 @@ def put_snowflake_array(
         """  # noqa: E501 - Line too long
         if values is not undefined.UNDEFINED:
             self[key] = [str(int(value)) for value in values]
+
+
+def cast_variants_array(cast: typing.Callable[[T_co], T], raw_values: typing.Iterable[T_co], /) -> typing.List[T]:
+    """Cast an array of enum variants while ignoring unrecognised variant types.
+
+    Parameters
+    ----------
+    cast : typing.Callable[[T_co], T]
+        Callback to cast each variant to.
+
+        This will ignore any variants which raises
+        `hikari.errors.UnrecognisedEntityError` on cast.
+    raw_values : typing.Iterable[T_co]
+        Iterable of the raw values to cast.
+
+    Returns
+    -------
+    typing.List[T]
+        A list of the casted variants (with any unrecognised types ignored).
+    """
+    results: typing.List[T] = []
+
+    for value in raw_values:
+        try:
+            results.append(cast(value))
+
+        except errors.UnrecognisedEntityError:
+            pass
+
+    return results
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index d38c744e..b3be760d 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -147,6 +147,13 @@ def init_logging(
 """Set of env variables which always indicate that ANSI flags should be included."""
 
 
+def _read_banner(package: str) -> str:
+    if sys.version_info >= (3, 9):
+        return importlib.resources.files(package).joinpath("banner.txt").open("r").read()
+    else:
+        return importlib.resources.read_text(package, "banner.txt")
+
+
 def print_banner(
     package: typing.Optional[str],
     allow_color: bool,
@@ -188,7 +195,7 @@ def print_banner(
     if package is None:
         return
 
-    raw_banner = importlib.resources.read_text(package, "banner.txt")
+    raw_banner = _read_banner(package)
 
     system_bits = (platform.machine(), platform.system(), platform.release())
     filtered_system_bits = (s.strip() for s in system_bits if s.strip())


Test patch:
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index c04e9964..c3d2cd6f 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -1979,7 +1979,6 @@ class StubResponse:
 
         await rest_client._parse_ratelimits(route, response, live_attributes)
 
-        assert live_attributes.still_alive.call_count == 1
         live_attributes.buckets.update_rate_limits.assert_called_once_with(
             compiled_route=route,
             bucket_header="bucket_header",
@@ -2001,7 +2000,7 @@ class StubResponse:
         await rest_client._parse_ratelimits(route, response, live_attributes)
 
         response.json.assert_not_called()
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     async def test__parse_ratelimits_when_ratelimited(self, rest_client, exit_exception, live_attributes):
         class StubResponse:
@@ -2016,7 +2015,7 @@ async def json(self):
         with pytest.raises(exit_exception):
             await rest_client._parse_ratelimits(route, StubResponse(), live_attributes)
 
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     async def test__parse_ratelimits_when_unexpected_content_type(self, rest_client, live_attributes):
         class StubResponse:
@@ -2032,7 +2031,7 @@ async def read(self):
         with pytest.raises(errors.HTTPResponseError):
             await rest_client._parse_ratelimits(route, StubResponse(), live_attributes)
 
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     async def test__parse_ratelimits_when_global_ratelimit(self, rest_client, live_attributes):
         class StubResponse:
@@ -2067,7 +2066,7 @@ async def json(self):
         with pytest.raises(rest._RetryRequest):
             await rest_client._parse_ratelimits(route, StubResponse(), live_attributes)
 
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     async def test__parse_ratelimits_when_retry_after_is_close_enough(self, rest_client, live_attributes):
         class StubResponse:
@@ -2085,7 +2084,7 @@ async def json(self):
         with pytest.raises(rest._RetryRequest):
             await rest_client._parse_ratelimits(route, StubResponse(), live_attributes)
 
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     async def test__parse_ratelimits_when_retry_after_is_not_close_enough(self, rest_client, live_attributes):
         class StubResponse:
@@ -2101,7 +2100,7 @@ async def json(self):
         with pytest.raises(errors.RateLimitedError):
             await rest_client._parse_ratelimits(route, StubResponse(), live_attributes)
 
-        assert live_attributes.still_alive.call_count == 0
+        live_attributes.still_alive.assert_not_called()
 
     #############
     # Endpoints #
@@ -2929,6 +2928,20 @@ async def test_fetch_channel_webhooks(self, rest_client):
             [mock.call({"id": "456"}), mock.call({"id": "789"})]
         )
 
+    async def test_fetch_channel_webhooks_ignores_unrecognised_webhook_type(self, rest_client):
+        webhook1 = StubModel(456)
+        expected_route = routes.GET_CHANNEL_WEBHOOKS.compile(channel=123)
+        rest_client._request = mock.AsyncMock(return_value=[{"id": "456"}, {"id": "789"}])
+        rest_client._entity_factory.deserialize_webhook = mock.Mock(
+            side_effect=[errors.UnrecognisedEntityError("yeet"), webhook1]
+        )
+
+        assert await rest_client.fetch_channel_webhooks(StubModel(123)) == [webhook1]
+        rest_client._request.assert_awaited_once_with(expected_route)
+        rest_client._entity_factory.deserialize_webhook.assert_has_calls(
+            [mock.call({"id": "456"}), mock.call({"id": "789"})]
+        )
+
     async def test_fetch_guild_webhooks(self, rest_client):
         webhook1 = StubModel(456)
         webhook2 = StubModel(789)
@@ -2943,6 +2956,20 @@ async def test_fetch_guild_webhooks(self, rest_client):
             [mock.call({"id": "456"}), mock.call({"id": "789"})]
         )
 
+    async def test_fetch_guild_webhooks_ignores_unrecognised_webhook_types(self, rest_client):
+        webhook1 = StubModel(456)
+        expected_route = routes.GET_GUILD_WEBHOOKS.compile(guild=123)
+        rest_client._request = mock.AsyncMock(return_value=[{"id": "456"}, {"id": "789"}])
+        rest_client._entity_factory.deserialize_webhook = mock.Mock(
+            side_effect=[errors.UnrecognisedEntityError("meow meow"), webhook1]
+        )
+
+        assert await rest_client.fetch_guild_webhooks(StubModel(123)) == [webhook1]
+        rest_client._request.assert_awaited_once_with(expected_route)
+        rest_client._entity_factory.deserialize_webhook.assert_has_calls(
+            [mock.call({"id": "456"}), mock.call({"id": "789"})]
+        )
+
     async def test_edit_webhook(self, rest_client):
         webhook = StubModel(456)
         expected_route = routes.PATCH_WEBHOOK_WITH_TOKEN.compile(webhook=123, token="token")
@@ -4105,6 +4132,21 @@ async def test_fetch_guild_channels(self, rest_client):
             [mock.call({"id": "456"}), mock.call({"id": "789"})]
         )
 
+    async def test_fetch_guild_channels_ignores_unknown_channel_type(self, rest_client):
+        channel1 = StubModel(456)
+        expected_route = routes.GET_GUILD_CHANNELS.compile(guild=123)
+        rest_client._request = mock.AsyncMock(return_value=[{"id": "456"}, {"id": "789"}])
+        rest_client._entity_factory.deserialize_channel = mock.Mock(
+            side_effect=[errors.UnrecognisedEntityError("echelon"), channel1]
+        )
+
+        assert await rest_client.fetch_guild_channels(StubModel(123)) == [channel1]
+
+        rest_client._request.assert_awaited_once_with(expected_route)
+        rest_client._entity_factory.deserialize_channel.assert_has_calls(
+            [mock.call({"id": "456"}), mock.call({"id": "789"})]
+        )
+
     async def test_create_guild_text_channel(self, rest_client: rest.RESTClientImpl):
         guild = StubModel(123)
         category_channel = StubModel(789)
@@ -5263,6 +5305,23 @@ async def test_fetch_application_commands_without_guild(self, rest_client):
         rest_client._request.assert_awaited_once_with(expected_route, query={"with_localizations": "true"})
         rest_client._entity_factory.deserialize_command.assert_called_once_with({"id": "34512312"}, guild_id=None)
 
+    async def test_fetch_application_commands_ignores_unknown_command_types(self, rest_client):
+        mock_command = mock.Mock()
+        expected_route = routes.GET_APPLICATION_GUILD_COMMANDS.compile(application=54123, guild=432234)
+        rest_client._entity_factory.deserialize_command.side_effect = [
+            errors.UnrecognisedEntityError("eep"),
+            mock_command,
+        ]
+        rest_client._request = mock.AsyncMock(return_value=[{"id": "541234"}, {"id": "553234"}])
+
+        result = await rest_client.fetch_application_commands(StubModel(54123), StubModel(432234))
+
+        assert result == [mock_command]
+        rest_client._request.assert_awaited_once_with(expected_route, query={"with_localizations": "true"})
+        rest_client._entity_factory.deserialize_command.assert_has_calls(
+            [mock.call({"id": "541234"}, guild_id=432234), mock.call({"id": "553234"}, guild_id=432234)]
+        )
+
     async def test__create_application_command_with_optionals(self, rest_client: rest.RESTClientImpl):
         expected_route = routes.POST_APPLICATION_GUILD_COMMAND.compile(application=4332123, guild=653452134)
         rest_client._request = mock.AsyncMock(return_value={"id": "29393939"})
@@ -5426,6 +5485,30 @@ async def test_set_application_commands_without_guild(self, rest_client):
         rest_client._request.assert_awaited_once_with(expected_route, json=[mock_command_builder.build.return_value])
         mock_command_builder.build.assert_called_once_with(rest_client._entity_factory)
 
+    async def test_set_application_commands_without_guild_handles_unknown_command_types(self, rest_client):
+        mock_command = mock.Mock()
+        expected_route = routes.PUT_APPLICATION_GUILD_COMMANDS.compile(application=532123123, guild=453123)
+        rest_client._entity_factory.deserialize_command.side_effect = [
+            errors.UnrecognisedEntityError("meow"),
+            mock_command,
+        ]
+        rest_client._request = mock.AsyncMock(return_value=[{"id": "435765"}, {"id": "4949493933"}])
+        mock_command_builder = mock.Mock()
+
+        result = await rest_client.set_application_commands(
+            StubModel(532123123), [mock_command_builder], StubModel(453123)
+        )
+
+        assert result == [mock_command]
+        rest_client._entity_factory.deserialize_command.assert_has_calls(
+            [
+                mock.call({"id": "435765"}, guild_id=453123),
+                mock.call({"id": "4949493933"}, guild_id=453123),
+            ]
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, json=[mock_command_builder.build.return_value])
+        mock_command_builder.build.assert_called_once_with(rest_client._entity_factory)
+
     async def test_edit_application_command_with_optionals(self, rest_client):
         expected_route = routes.PATCH_APPLICATION_GUILD_COMMAND.compile(
             application=1235432, guild=54123, command=3451231
@@ -5799,6 +5882,25 @@ async def test_fetch_scheduled_events(self, rest_client: rest.RESTClientImpl):
         )
         rest_client._request.assert_awaited_once_with(expected_route, query={"with_user_count": "true"})
 
+    async def test_fetch_scheduled_events_handles_unrecognised_events(self, rest_client: rest.RESTClientImpl):
+        mock_event = mock.Mock()
+        rest_client._entity_factory.deserialize_scheduled_event.side_effect = [
+            errors.UnrecognisedEntityError("evil laugh"),
+            mock_event,
+        ]
+        expected_route = routes.GET_GUILD_SCHEDULED_EVENTS.compile(guild=65234123)
+        rest_client._request = mock.AsyncMock(
+            return_value=[{"id": "432234", "type": "1"}, {"id": "4939394", "type": "494949"}]
+        )
+
+        result = await rest_client.fetch_scheduled_events(StubModel(65234123))
+
+        assert result == [mock_event]
+        rest_client._entity_factory.deserialize_scheduled_event.assert_has_calls(
+            [mock.call({"id": "432234", "type": "1"}), mock.call({"id": "4939394", "type": "494949"})]
+        )
+        rest_client._request.assert_awaited_once_with(expected_route, query={"with_user_count": "true"})
+
     async def test_create_stage_event(self, rest_client: rest.RESTClientImpl, file_resource_patch):
         expected_route = routes.POST_GUILD_SCHEDULED_EVENT.compile(guild=123321)
         rest_client._request = mock.AsyncMock(return_value={"id": "494949", "name": "MEOsdasdWWWWW"})
diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py
index fbbcb563..db3091e2 100644
--- a/tests/hikari/internal/test_ux.py
+++ b/tests/hikari/internal/test_ux.py
@@ -144,6 +144,42 @@ def test_when_doesnt_support_color(self):
         supports_color.assert_called_once_with(True, False)
 
 
+class TestRedBanner:
+    def test_when_above_3_9(self):
+        class MockTraversable:
+            joint_path = None
+            open_mode = None
+
+            def joinpath(self, path):
+                self.joint_path = path
+                return self
+
+            def open(self, mode):
+                self.open_mode = mode
+                return self
+
+            def read(self):
+                return read
+
+        traversable = MockTraversable()
+        read = object()
+
+        with mock.patch.object(sys, "version_info", new=(3, 9)):
+            with mock.patch.object(importlib.resources, "files", return_value=traversable, create=True) as read_text:
+                assert ux._read_banner("hikaru") is read
+
+        read_text.assert_called_once_with("hikaru")
+        assert traversable.joint_path == "banner.txt"
+        assert traversable.open_mode == "r"
+
+    def test_when_bellow_3_9(self):
+        with mock.patch.object(sys, "version_info", new=(2, 7)):
+            with mock.patch.object(importlib.resources, "read_text") as read_text:
+                assert ux._read_banner("hikaru") is read_text.return_value
+
+        read_text.assert_called_once_with("hikaru", "banner.txt")
+
+
 class TestPrintBanner:
     def test_when_package_is_none(self):
         with mock.patch.object(sys.stdout, "write") as write:
@@ -179,7 +215,7 @@ def test_when_supports_color(self, mock_args):
         )
         stack.enter_context(mock.patch.object(time, "sleep"))
         supports_color = stack.enter_context(mock.patch.object(ux, "supports_color", return_value=True))
-        read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        read_banner = stack.enter_context(mock.patch.object(ux, "_read_banner"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
         builtins_open = stack.enter_context(mock.patch.object(builtins, "open"))
         abspath = stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
@@ -187,7 +223,7 @@ def test_when_supports_color(self, mock_args):
         fileno = stack.enter_context(mock.patch.object(sys.stdout, "fileno"))
 
         with stack:
-            ux.print_banner("hikari", True, False)
+            ux.print_banner("hikaru", True, False)
 
         args = {
             # Hikari stuff.
@@ -207,7 +243,8 @@ def test_when_supports_color(self, mock_args):
             "blue": 2,
         }
 
-        template.assert_called_once_with(read_text())
+        read_banner.assert_called_once_with("hikaru")
+        template.assert_called_once_with(read_banner.return_value)
         template().safe_substitute.assert_called_once_with(args)
         builtins_open.assert_called_once_with(fileno.return_value, "w", encoding="utf-8", closefd=False)
         builtins_open.return_value.__enter__.return_value.write.assert_called_once_with(template().safe_substitute())
@@ -222,7 +259,7 @@ def test_when_doesnt_supports_color(self, mock_args):
         )
         stack.enter_context(mock.patch.object(time, "sleep"))
         supports_color = stack.enter_context(mock.patch.object(ux, "supports_color", return_value=False))
-        read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        read_banner = stack.enter_context(mock.patch.object(ux, "_read_banner"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
         abspath = stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
         dirname = stack.enter_context(mock.patch.object(os.path, "dirname"))
@@ -230,7 +267,7 @@ def test_when_doesnt_supports_color(self, mock_args):
         fileno = stack.enter_context(mock.patch.object(sys.stdout, "fileno"))
 
         with stack:
-            ux.print_banner("hikari", True, False)
+            ux.print_banner("hikaru", True, False)
 
         args = {
             # Hikari stuff.
@@ -250,7 +287,7 @@ def test_when_doesnt_supports_color(self, mock_args):
             "blue": "",
         }
 
-        template.assert_called_once_with(read_text())
+        template.assert_called_once_with(read_banner.return_value)
         template().safe_substitute.assert_called_once_with(args)
         dirname.assert_called_once_with("~/hikari")
         abspath.assert_called_once_with(dirname())
@@ -262,7 +299,7 @@ def test_use_extra_args(self, mock_args):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(colorlog.escape_codes, "escape_codes", new={}))
         stack.enter_context(mock.patch.object(time, "sleep"))
-        read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        read_banner = stack.enter_context(mock.patch.object(ux, "_read_banner"))
         template = stack.enter_context(mock.patch.object(string, "Template"))
         builtins_open = stack.enter_context(mock.patch.object(builtins, "open"))
         stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
@@ -274,7 +311,7 @@ def test_use_extra_args(self, mock_args):
         }
 
         with stack:
-            ux.print_banner("hikari", True, False, extra_args=extra_args)
+            ux.print_banner("hikaru", True, False, extra_args=extra_args)
 
         args = {
             # Hikari stuff.
@@ -293,7 +330,8 @@ def test_use_extra_args(self, mock_args):
 
         args.update(extra_args)
 
-        template.assert_called_once_with(read_text())
+        read_banner.assert_called_once_with("hikaru")
+        template.assert_called_once_with(read_banner.return_value)
         template().safe_substitute.assert_called_once_with(args)
         builtins_open.assert_called_once_with(fileno.return_value, "w", encoding="utf-8", closefd=False)
         builtins_open.return_value.__enter__.return_value.write.assert_called_once_with(template().safe_substitute())
@@ -302,7 +340,7 @@ def test_overwrite_args_raises_error(self, mock_args):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(time, "sleep"))
         stack.enter_context(mock.patch.object(colorlog.escape_codes, "escape_codes", new={}))
-        stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        stack.enter_context(mock.patch.object(ux, "_read_banner"))
         stack.enter_context(mock.patch.object(string, "Template"))
         stack.enter_context(mock.patch.object(sys.stdout, "write"))
         stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))