Repository: redis/redis-py

Issue:
SHUTDOWN - add support for the new `NOW`, `FORCE` and `ABORT` modifiers



Discussion:

Resolving patch:
diff --git a/redis/commands/core.py b/redis/commands/core.py
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -1109,12 +1109,25 @@ def save(self, **kwargs) -> ResponseT:
         """
         return self.execute_command("SAVE", **kwargs)
 
-    def shutdown(self, save: bool = False, nosave: bool = False, **kwargs) -> None:
+    def shutdown(
+        self,
+        save: bool = False,
+        nosave: bool = False,
+        now: bool = False,
+        force: bool = False,
+        abort: bool = False,
+        **kwargs,
+    ) -> None:
         """Shutdown the Redis server.  If Redis has persistence configured,
-        data will be flushed before shutdown.  If the "save" option is set,
-        a data flush will be attempted even if there is no persistence
-        configured.  If the "nosave" option is set, no data flush will be
-        attempted.  The "save" and "nosave" options cannot both be set.
+        data will be flushed before shutdown.
+        It is possible to specify modifiers to alter the behavior of the command:
+        ``save`` will force a DB saving operation even if no save points are configured.
+        ``nosave`` will prevent a DB saving operation even if one or more save points
+        are configured.
+        ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in
+        the shutdown sequence.
+        ``force`` ignores any errors that would normally prevent the server from exiting
+        ``abort`` cancels an ongoing shutdown and cannot be combined with other flags.
 
         For more information see https://redis.io/commands/shutdown
         """
@@ -1125,6 +1138,12 @@ def shutdown(self, save: bool = False, nosave: bool = False, **kwargs) -> None:
             args.append("SAVE")
         if nosave:
             args.append("NOSAVE")
+        if now:
+            args.append("NOW")
+        if force:
+            args.append("FORCE")
+        if abort:
+            args.append("ABORT")
         try:
             self.execute_command(*args, **kwargs)
         except ConnectionError:
@@ -1235,7 +1254,13 @@ async def memory_help(self, **kwargs) -> None:
         return super().memory_help(**kwargs)
 
     async def shutdown(
-        self, save: bool = False, nosave: bool = False, **kwargs
+        self,
+        save: bool = False,
+        nosave: bool = False,
+        now: bool = False,
+        force: bool = False,
+        abort: bool = False,
+        **kwargs,
     ) -> None:
         """Shutdown the Redis server.  If Redis has persistence configured,
         data will be flushed before shutdown.  If the "save" option is set,
@@ -1252,6 +1277,12 @@ async def shutdown(
             args.append("SAVE")
         if nosave:
             args.append("NOSAVE")
+        if now:
+            args.append("NOW")
+        if force:
+            args.append("FORCE")
+        if abort:
+            args.append("ABORT")
         try:
             await self.execute_command(*args, **kwargs)
         except ConnectionError:


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -3,6 +3,7 @@
 import re
 import time
 from string import ascii_letters
+from unittest import mock
 
 import pytest
 
@@ -4633,6 +4634,19 @@ def test_replicaof(self, r):
             assert r.replicaof("NO ONE")
         assert r.replicaof("NO", "ONE")
 
+    def test_shutdown(self, r: redis.Redis):
+        r.execute_command = mock.MagicMock()
+        r.execute_command("SHUTDOWN", "NOSAVE")
+        r.execute_command.assert_called_once_with("SHUTDOWN", "NOSAVE")
+
+    @skip_if_server_version_lt("7.0.0")
+    def test_shutdown_with_params(self, r: redis.Redis):
+        r.execute_command = mock.MagicMock()
+        r.execute_command("SHUTDOWN", "SAVE", "NOW", "FORCE")
+        r.execute_command.assert_called_once_with("SHUTDOWN", "SAVE", "NOW", "FORCE")
+        r.execute_command("SHUTDOWN", "ABORT")
+        r.execute_command.assert_called_with("SHUTDOWN", "ABORT")
+
     @pytest.mark.replica
     @skip_if_server_version_lt("2.8.0")
     @skip_if_redis_enterprise()