Repository: redis/redis-py

Issue:
SentinelManagedConnection.read_response() got an unexpected keyword argument 'push_request'
Version: 3.5.3
Platform: Python 3.10 on MacOS 13.3.1

Description:
[redis.client.PubSub().parse_response()](https://github.com/redis/redis-py/blob/master/redis/client.py#L777) is calling [SentinelManagedConnection.read_response()](https://github.com/redis/redis-py/blob/master/redis/sentinel.py#L57) with a non-existent `push_request` keyword argument.

```
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 1108, in run
    pubsub.get_message(ignore_subscribe_messages=True, timeout=sleep_time)
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 983, in get_message
    response = self.parse_response(block=(timeout is None), timeout=timeout)
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 796, in parse_response
    response = self._execute(conn, try_read)
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 772, in _execute
    return conn.retry.call_with_retry(
  File "/usr/local/lib/python3.10/site-packages/redis/retry.py", line 46, in call_with_retry
    return do()
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 773, in <lambda>
  File "/usr/local/lib/python3.10/site-packages/redis/client.py", line 794, in try_read
    return conn.read_response(disconnect_on_error=False, push_request=True)
TypeError: SentinelManagedConnection.read_response() got an unexpected keyword argument 'push_request'
```

This issue looks identical to #2754 

Discussion:

Patch:
diff --git a/redis/asyncio/sentinel.py b/redis/asyncio/sentinel.py
index 501e234..5ed9240 100644
--- a/redis/asyncio/sentinel.py
+++ b/redis/asyncio/sentinel.py
@@ -335,11 +335,15 @@ class Sentinel(AsyncSentinelCommands):
         kwargs["is_master"] = True
         connection_kwargs = dict(self.connection_kwargs)
         connection_kwargs.update(kwargs)
-        return redis_class(
-            connection_pool=connection_pool_class(
-                service_name, self, **connection_kwargs
-            )
+
+        connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
+        # The Redis object "owns" the pool
+        auto_close_connection_pool = True
+        client = redis_class(
+            connection_pool=connection_pool,
         )
+        client.auto_close_connection_pool = auto_close_connection_pool
+        return client
 
     def slave_for(
         self,
@@ -368,8 +372,12 @@ class Sentinel(AsyncSentinelCommands):
         kwargs["is_master"] = False
         connection_kwargs = dict(self.connection_kwargs)
         connection_kwargs.update(kwargs)
-        return redis_class(
-            connection_pool=connection_pool_class(
-                service_name, self, **connection_kwargs
-            )
+
+        connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
+        # The Redis object "owns" the pool
+        auto_close_connection_pool = True
+        client = redis_class(
+            connection_pool=connection_pool,
         )
+        client.auto_close_connection_pool = auto_close_connection_pool
+        return client
diff --git a/redis/sentinel.py b/redis/sentinel.py
index 0ba179b..836e781 100644
--- a/redis/sentinel.py
+++ b/redis/sentinel.py
@@ -55,12 +55,17 @@ class SentinelManagedConnection(Connection):
         return self.retry.call_with_retry(self._connect_retry, lambda error: None)
 
     def read_response(
-        self, disable_decoding=False, *, disconnect_on_error: Optional[bool] = False
+        self,
+        disable_decoding=False,
+        *,
+        disconnect_on_error: Optional[bool] = False,
+        push_request: Optional[bool] = False,
     ):
         try:
             return super().read_response(
                 disable_decoding=disable_decoding,
                 disconnect_on_error=disconnect_on_error,
+                push_request=push_request,
             )
         except ReadOnlyError:
             if self.connection_pool.is_master:


Test patch:
diff --git a/tests/test_asyncio/test_sentinel.py b/tests/test_asyncio/test_sentinel.py
index 2091f2c..a2d52f1 100644
--- a/tests/test_asyncio/test_sentinel.py
+++ b/tests/test_asyncio/test_sentinel.py
@@ -1,4 +1,5 @@
 import socket
+from unittest import mock
 
 import pytest
 import pytest_asyncio
@@ -239,3 +240,28 @@ async def test_flushconfig(cluster, sentinel):
 async def test_reset(cluster, sentinel):
     cluster.master["is_odown"] = True
     assert await sentinel.sentinel_reset("mymaster")
+
+
+@pytest.mark.onlynoncluster
+@pytest.mark.parametrize("method_name", ["master_for", "slave_for"])
+async def test_auto_close_pool(cluster, sentinel, method_name):
+    """
+    Check that the connection pool created by the sentinel client is
+    automatically closed
+    """
+
+    method = getattr(sentinel, method_name)
+    client = method("mymaster", db=9)
+    pool = client.connection_pool
+    assert client.auto_close_connection_pool is True
+    calls = 0
+
+    async def mock_disconnect():
+        nonlocal calls
+        calls += 1
+
+    with mock.patch.object(pool, "disconnect", mock_disconnect):
+        await client.close()
+
+    assert calls == 1
+    await pool.disconnect()