Repository: hikari-py/hikari
Pull request #2369 (2025-05-17 22:00:37)

Add `startup_window_delay`


Patch:
diff --git a/changes/2369.feature.md b/changes/2369.feature.md
new file mode 100644
index 00000000..53a672c2
--- /dev/null
+++ b/changes/2369.feature.md
@@ -0,0 +1,1 @@
+Add `startup_window_delay` argument to `.run` and `.start` to customize the time in between startup windows for shards
diff --git a/hikari/impl/gateway_bot.py b/hikari/impl/gateway_bot.py
index b131eb66..60c08210 100644
--- a/hikari/impl/gateway_bot.py
+++ b/hikari/impl/gateway_bot.py
@@ -693,7 +693,7 @@ def print_banner(
         ux.print_banner(banner, allow_color=allow_color, force_color=force_color, extra_args=extra_args)
 
     @typing_extensions.override
-    def run(
+    def run(  # noqa: PLR0912 - Too many branches
         self,
         *,
         activity: presences.Activity | None = None,
@@ -711,6 +711,7 @@ def run(
         status: presences.Status = presences.Status.ONLINE,
         shard_ids: typing.Sequence[int] | None = None,
         shard_count: int | None = None,
+        startup_window_delay: int = 5,
     ) -> None:
         """Start the application and block until it's finished running.
 
@@ -795,6 +796,8 @@ def run(
 
             Defaults to [`None`][] which results in the count being
             determined dynamically on startup.
+        startup_window_delay
+            The time in seconds to wait in between shard startup windows.
         status
             The initial status to show for the user presence on startup.
 
@@ -804,6 +807,8 @@ def run(
             If bot is already running.
         TypeError
             If `shard_ids` is passed without `shard_count`.
+        ValueError
+            If `startup_window_delay` is less than 5 seconds.
         """
         if self._closed_event:
             msg = "bot is already running"
@@ -813,6 +818,10 @@ def run(
             msg = "'shard_ids' must be passed with 'shard_count'"
             raise TypeError(msg)
 
+        if startup_window_delay < 5:
+            msg = "'startup_window_delay' must be at least 5 seconds"
+            raise ValueError(msg)
+
         loop = aio.get_or_make_loop()
 
         if asyncio_debug:
@@ -840,6 +849,7 @@ def run(
                         shard_ids=shard_ids,
                         shard_count=shard_count,
                         status=status,
+                        startup_window_delay=startup_window_delay,
                     )
                 )
 
@@ -880,6 +890,7 @@ async def start(
         shard_ids: typing.Sequence[int] | None = None,
         shard_count: int | None = None,
         status: presences.Status = presences.Status.ONLINE,
+        startup_window_delay: int = 5,
     ) -> None:
         """Start the bot, wait for all shards to become ready, and then return.
 
@@ -919,6 +930,8 @@ async def start(
 
             Defaults to [`None`][] which results in the count being
             determined dynamically on startup.
+        startup_window_delay
+            The time in seconds to wait in between shard startup windows.
         status
             The initial status to show for the user presence on startup.
 
@@ -926,6 +939,8 @@ async def start(
         ------
         TypeError
             If `shard_ids` is passed without `shard_count`.
+        ValueError
+            If `startup_window_delay` is less than 5 seconds.
         hikari.errors.ComponentStateConflictError
             If bot is already running.
         """
@@ -937,6 +952,10 @@ async def start(
             msg = "'shard_ids' must be passed with 'shard_count'"
             raise TypeError(msg)
 
+        if startup_window_delay < 5:
+            msg = "'startup_window_delay' must be at least 5 seconds"
+            raise ValueError(msg)
+
         _validate_activity(activity)
 
         start_time = time.monotonic()
@@ -987,11 +1006,13 @@ async def start(
             shard_ids = shard_ids[max_concurrency:]
 
             if self._shards:
-                _LOGGER.info("the next startup window is in 5 seconds, please wait...")
+                _LOGGER.info("the next startup window is in %d seconds, please wait...", startup_window_delay)
 
                 try:
                     await aio.first_completed(
-                        self._closing_event.wait(), *(shard.join() for shard in self._shards.values()), timeout=5
+                        self._closing_event.wait(),
+                        *(shard.join() for shard in self._shards.values()),
+                        timeout=startup_window_delay,
                     )
 
                     if self._closing_event.is_set():
diff --git a/pyproject.toml b/pyproject.toml
index cacce80f..21e60a4a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -147,6 +147,7 @@ exclude_lines = [
     "^if __name__ == ['\"]__main__['\"]:$",
     "^\\s*\\.\\.\\.$",
     "^\\s*@abc.abstractmethod$",
+    "^\\s*@typing.overload$",
     "^\\s*if typing.TYPE_CHECKING:$",
     # Whatever is in inside `if not typing.TYPE_CHECKING:` is expected to be tested, but the branch will always run, making
     # coverage think that its only partial


Test patch:
diff --git a/tests/hikari/impl/test_gateway_bot.py b/tests/hikari/impl/test_gateway_bot.py
index cbfd7b05..13601498 100644
--- a/tests/hikari/impl/test_gateway_bot.py
+++ b/tests/hikari/impl/test_gateway_bot.py
@@ -604,6 +604,7 @@ def test_run(self, bot):
                 shard_ids=shard_ids,
                 shard_count=shard_count,
                 status=status,
+                startup_window_delay=15,
             )
 
         loop.run_until_complete.assert_has_calls(
@@ -619,6 +620,7 @@ def test_run(self, bot):
             shard_ids=shard_ids,
             shard_count=shard_count,
             status=status,
+            startup_window_delay=15,
         )
         handle_interrupts.assert_called_once_with(enabled=False, loop=loop, propagate_interrupts=False)
         handle_interrupts.return_value.assert_used_once()
@@ -681,6 +683,7 @@ def _mock_start_one_shard(*args, **kwargs):
                 idle_since="some idle since",
                 status="some status",
                 large_threshold=500,
+                startup_window_delay=10,
             )
 
         check_for_updates.assert_called_once_with(http_settings, proxy_settings)
@@ -727,7 +730,7 @@ def _mock_start_one_shard(*args, **kwargs):
                 # Shard 1
                 mock.call(closing_event_closing_wait, gather.return_value),
                 # Shard 2
-                mock.call(closing_event_closing_wait, shard1.join.return_value, timeout=5),
+                mock.call(closing_event_closing_wait, shard1.join.return_value, timeout=10),
                 mock.call(closing_event_closing_wait, gather.return_value),
             ]
         )