Repository: hikari-py/hikari
Pull request #711 (2021-08-01 17:20:28)

Split bot.close into internal and external functions
  - Some people were getting confused as to why bot.close was getting called 2+ times

### 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.


Patch:
diff --git a/dev-requirements.txt b/dev-requirements.txt
index d19d8666..34832733 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -9,7 +9,7 @@ mock==4.0.3
 pytest==6.2.4
 pytest-asyncio==0.15.1
 pytest-cov==2.12.1
-pytest-randomly==3.8.0
+pytest-randomly==3.10.1
 
 # Coverage testing.
 coverage==6.0b1
diff --git a/flake8-requirements.txt b/flake8-requirements.txt
index 12c793e9..cb687883 100644
--- a/flake8-requirements.txt
+++ b/flake8-requirements.txt
@@ -9,7 +9,7 @@ flake8-black==0.2.3                     # runs black
 flake8-broken-line==0.3.0               # forbey "\" linebreaks
 flake8-builtins==1.5.3                  # builtin shadowing checks
 flake8-coding==1.3.2                    # coding magic-comment detection
-flake8-comprehensions==3.5.0            # comprehension checks
+flake8-comprehensions==3.6.1            # comprehension checks
 flake8-deprecated==1.3                  # deprecated call checks
 flake8-docstrings==1.6.0                # pydocstyle support
 flake8-executable==2.1.1                # shebangs
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index a0bf9bbe..ab31b18a 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -360,7 +360,9 @@ def get_me(self) -> typing.Optional[users_.OwnUser]:
     async def close(self) -> None:
         """Kill the application by shutting all components down."""
         self._check_if_alive()
+        await self._close()
 
+    async def _close(self) -> None:
         if self._closed_event:  # Closing is in progress from another call, wait for that to complete.
             await self._closed_event.wait()
             return
@@ -658,7 +660,7 @@ def handle_os_interrupt(signum: int, frame: types.FrameType) -> None:
 
         finally:
             try:
-                loop.run_until_complete(self.close())
+                loop.run_until_complete(self._close())
 
                 if close_passed_executor and self._executor is not None:
                     _LOGGER.debug("shutting down executor %s", self._executor)
@@ -829,7 +831,7 @@ async def start(
                     # If any shards stopped silently, we should close.
                     if any(not s.is_alive for s in self._shards.values()):
                         _LOGGER.warning("one of the shards has been manually shut down (no error), will now shut down")
-                        await self.close()
+                        await self._close()
                         return
                     # new window starts.
 
@@ -946,7 +948,7 @@ async def _set_close_flag(self, signame: str, signum: int) -> None:
         # solution.
         _LOGGER.debug("received interrupt %s (%s), will start shutting down shortly", signame, signum)
 
-        await self.close()
+        await self._close()
 
     async def _start_one_shard(
         self,


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index a0609fd9..9958cee1 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -278,28 +278,37 @@ def test_check_if_alive(self, bot):
         bot._check_if_alive()
 
     @pytest.mark.asyncio()
-    async def test_close_when_already_closed(self, bot, event_manager):
+    async def test_close(self, bot):
+        with mock.patch.object(bot_impl.GatewayBot, "_close") as internal_close:
+            with mock.patch.object(bot_impl.GatewayBot, "_check_if_alive") as check_if_alive:
+                await bot.close()
+
+        check_if_alive.assert_called_once_with()
+        internal_close.assert_awaited_once_with()
+
+    @pytest.mark.asyncio()
+    async def test__close_when_already_closed(self, bot):
         bot._closing_event = None
         bot._closed_event = None
         bot._is_alive = True
 
-        await bot.close()
+        await bot._close()
 
         assert bot._is_alive is True
 
     @pytest.mark.asyncio()
-    async def test_close_when_already_closing(self, bot, event_manager):
+    async def test__close_when_already_closing(self, bot):
         bot._closing_event = object()
         bot._closed_event = mock.AsyncMock()
         bot._is_alive = True
 
-        await bot.close()
+        await bot._close()
 
         bot._closed_event.wait.assert_awaited_once_with()
         assert bot._is_alive is True
 
     @pytest.mark.asyncio()
-    async def test_close(self, bot, event_manager, event_factory, rest, voice, cache):
+    async def test__close(self, bot, event_manager, event_factory, rest, voice, cache):
         def null_call(arg):
             return arg
 
@@ -344,7 +353,7 @@ def assert_awaited_once(self):
         bot._shards = {0: shard0, 1: shard1, 2: shard2}
 
         with stack:
-            await bot.close()
+            await bot._close()
 
         # Events and args
         closing_event.set.assert_called_once_with()
@@ -473,7 +482,7 @@ def test_run_with_asyncio_debug(self, bot):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         loop = stack.enter_context(mock.patch.object(aio, "get_or_make_loop")).return_value
 
         with stack:
@@ -485,7 +494,7 @@ def test_run_with_coroutine_tracking_depth(self, bot):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         stack.enter_context(mock.patch.object(aio, "get_or_make_loop"))
         coroutine_tracking_depth = stack.enter_context(
             mock.patch.object(sys, "set_coroutine_origin_tracking_depth", side_effect=AttributeError)
@@ -500,7 +509,7 @@ def test_run_with_enable_signal_handlers(self, bot):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         stack.enter_context(mock.patch.object(aio, "get_or_make_loop"))
         signal_function = stack.enter_context(
             mock.patch.object(signal, "signal", side_effect=[None, AttributeError, None, AttributeError])
@@ -526,7 +535,7 @@ def raise_signal(*args, **kwargs):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=raise_signal))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl, "_LOGGER", isEnabledFor=mock.Mock(return_value=logging)))
         loop = stack.enter_context(mock.patch.object(aio, "get_or_make_loop")).return_value
         set_close_flag = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_set_close_flag", new=mock.Mock()))
@@ -543,7 +552,7 @@ def test_run_with_close_passed_executor(self, bot):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         stack.enter_context(mock.patch.object(aio, "get_or_make_loop"))
         executor = mock.Mock()
         bot._executor = executor
@@ -558,7 +567,7 @@ def test_run_with_close_loop(self, bot):
         stack = contextlib.ExitStack()
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         destroy_loop = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_destroy_loop"))
         loop = stack.enter_context(mock.patch.object(aio, "get_or_make_loop")).return_value
 
@@ -581,7 +590,7 @@ def test_run(self, bot):
         stack = contextlib.ExitStack()
         start_function = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "start", new=mock.Mock()))
         join_function = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "join", new=mock.Mock()))
-        close_function = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "close", new=mock.Mock()))
+        close_function = stack.enter_context(mock.patch.object(bot_impl.GatewayBot, "_close", new=mock.Mock()))
         loop = stack.enter_context(mock.patch.object(aio, "get_or_make_loop")).return_value
 
         with stack:
@@ -768,7 +777,7 @@ async def test_request_guild_members(self, bot):
 
     @pytest.mark.asyncio()
     async def test_set_close_flag(self, bot):
-        with mock.patch.object(bot_impl.GatewayBot, "close") as close:
+        with mock.patch.object(bot_impl.GatewayBot, "_close") as close:
             await bot._set_close_flag("Terminated", 15)
 
         close.assert_awaited_once_with()