internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: redis/redis-py

Issue:
`connection.can_read()` raises OSError when connection is broken

**Version**: 4.2.2

**Platform**: Windows 11
**Description**: 
`redis.asyncio.connection.Connection()`, the class provides a `can_read` method, which is used, for example, in `Client.parse_response`.

However, unlike `Connection.read_response()` there is no error handling present to turn any OSErrors into `asyncio.ConnectionError`.
And so, the OSError bubbles up and causes problems, where we expect only `asyncio.ConnectionError` to occur.
This plays havoc with retry-mechanisms and automatic reconnect.

The issue is evident when the connection to the redis server is interrupted during a blocking call to `pubsub.run()` where a `get_message` call is made with a positive timeout. 




Discussion:

Resolving patch:
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -911,7 +911,13 @@ async def can_read(self, timeout: float = 0):
         """Poll the socket to see if there's data that can be read."""
         if not self.is_connected:
             await self.connect()
-        return await self._parser.can_read(timeout)
+        try:
+            return await self._parser.can_read(timeout)
+        except OSError as e:
+            await self.disconnect()
+            raise ConnectionError(
+                f"Error while reading from {self.host}:{self.port}: {e.args}"
+            )
 
     async def read_response(self, disable_decoding: bool = False):
         """Read the response from a previously sent command"""
diff --git a/redis/connection.py b/redis/connection.py
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -808,7 +808,13 @@ def can_read(self, timeout=0):
         sock = self._sock
         if not sock:
             self.connect()
-        return self._parser.can_read(timeout)
+        try:
+            return self._parser.can_read(timeout)
+        except OSError as e:
+            self.disconnect()
+            raise ConnectionError(
+                f"Error while reading from {self.host}:{self.port}: {e.args}"
+            )
 
     def read_response(self, disable_decoding=False):
         """Read the response from a previously sent command"""


Test patch:
Repository: redis/redis-py

Issue:
Add support for COMMAND GETKEYSANDFLAGS



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
@@ -743,6 +743,14 @@ def command_info(self, **kwargs) -> None:
     def command_count(self, **kwargs) -> ResponseT:
         return self.execute_command("COMMAND COUNT", **kwargs)
 
+    def command_getkeysandflags(self, *args: List[str]) -> List[Union[str, List[str]]]:
+        """
+        Returns array of keys from a full Redis command and their usage flags.
+
+        For more information see https://redis.io/commands/command-getkeysandflags
+        """
+        return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
+
     def command_docs(self, *args):
         """
         This function throws a NotImplementedError since it is intentionally


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -4468,6 +4468,15 @@ def test_command(self, r):
         assert "set" in cmds
         assert "get" in cmds
 
+    @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
+    @skip_if_redis_enterprise()
+    def test_command_getkeysandflags(self, r: redis.Redis):
+        res = [["mylist1", ["RW", "access", "delete"]], ["mylist2", ["RW", "insert"]]]
+        assert res == r.command_getkeysandflags(
+            "LMOVE", "mylist1", "mylist2", "left", "left"
+        )
+
     @pytest.mark.onlynoncluster
     @skip_if_server_version_lt("4.0.0")
     @skip_if_redis_enterprise()
Repository: redis/redis-py

Issue:
CONFIG GET - add the ability to pass multiple pattern parameters in one call



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
@@ -760,13 +760,15 @@ def command_docs(self, *args):
             "COMMAND DOCS is intentionally not implemented in the client."
         )
 
-    def config_get(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
+    def config_get(
+        self, pattern: PatternT = "*", *args: List[PatternT], **kwargs
+    ) -> ResponseT:
         """
         Return a dictionary of configuration based on the ``pattern``
 
         For more information see https://redis.io/commands/config-get
         """
-        return self.execute_command("CONFIG GET", pattern, **kwargs)
+        return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
 
     def config_set(self, name: KeyT, value: EncodableT, **kwargs) -> ResponseT:
         """Set config item ``name`` with ``value``


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -669,6 +669,12 @@ def test_config_get(self, r):
         # # assert 'maxmemory' in data
         # assert data['maxmemory'].isdigit()
 
+    @skip_if_server_version_lt("7.0.0")
+    def test_config_get_multi_params(self, r: redis.Redis):
+        res = r.config_get("*max-*-entries*", "maxmemory")
+        assert "maxmemory" in res
+        assert "hash-max-listpack-entries" in res
+
     @pytest.mark.onlynoncluster
     @skip_if_redis_enterprise()
     def test_config_resetstat(self, r):
Repository: redis/redis-py

Issue:
CONFIG SET - add the ability to set multiple parameters in one call



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
@@ -770,12 +770,18 @@ def config_get(
         """
         return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
 
-    def config_set(self, name: KeyT, value: EncodableT, **kwargs) -> ResponseT:
+    def config_set(
+        self,
+        name: KeyT,
+        value: EncodableT,
+        *args: List[Union[KeyT, EncodableT]],
+        **kwargs,
+    ) -> ResponseT:
         """Set config item ``name`` with ``value``
 
         For more information see https://redis.io/commands/config-set
         """
-        return self.execute_command("CONFIG SET", name, value, **kwargs)
+        return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
 
     def config_resetstat(self, **kwargs) -> ResponseT:
         """


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -692,6 +692,16 @@ def test_config_set(self, r):
         assert r.config_set("timeout", 0)
         assert r.config_get()["timeout"] == "0"
 
+    @skip_if_server_version_lt("7.0.0")
+    @skip_if_redis_enterprise()
+    def test_config_set_multi_params(self, r: redis.Redis):
+        r.config_set("timeout", 70, "maxmemory", 100)
+        assert r.config_get()["timeout"] == "70"
+        assert r.config_get()["maxmemory"] == "100"
+        assert r.config_set("timeout", 0, "maxmemory", 0)
+        assert r.config_get()["timeout"] == "0"
+        assert r.config_get()["maxmemory"] == "0"
+
     @skip_if_server_version_lt("6.0.0")
     @skip_if_redis_enterprise()
     def test_failover(self, r):
Repository: redis/redis-py

Issue:
INFO - add support for taking multiple section arguments



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
@@ -900,7 +900,9 @@ def select(self, index: int, **kwargs) -> ResponseT:
         """
         return self.execute_command("SELECT", index, **kwargs)
 
-    def info(self, section: Union[str, None] = None, **kwargs) -> ResponseT:
+    def info(
+        self, section: Union[str, None] = None, *args: List[str], **kwargs
+    ) -> ResponseT:
         """
         Returns a dictionary containing information about the Redis server
 
@@ -915,7 +917,7 @@ def info(self, section: Union[str, None] = None, **kwargs) -> ResponseT:
         if section is None:
             return self.execute_command("INFO", **kwargs)
         else:
-            return self.execute_command("INFO", section, **kwargs)
+            return self.execute_command("INFO", section, *args, **kwargs)
 
     def lastsave(self, **kwargs) -> ResponseT:
         """


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -727,6 +727,14 @@ def test_info(self, r):
         assert "arch_bits" in info.keys()
         assert "redis_version" in info.keys()
 
+    @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
+    def test_info_multi_sections(self, r):
+        res = r.info("clients", "server")
+        assert isinstance(res, dict)
+        assert "redis_version" in res
+        assert "connected_clients" in res
+
     @pytest.mark.onlynoncluster
     @skip_if_redis_enterprise()
     def test_lastsave(self, r):
Repository: redis/redis-py

Issue:
Add support for MODULE LOADEX



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
@@ -5592,6 +5592,27 @@ def module_load(self, path, *args) -> ResponseT:
         """
         return self.execute_command("MODULE LOAD", path, *args)
 
+    def module_loadex(
+        self,
+        path: str,
+        options: Optional[List[str]] = None,
+        args: Optional[List[str]] = None,
+    ) -> ResponseT:
+        """
+        Loads a module from a dynamic library at runtime with configuration directives.
+
+        For more information see https://redis.io/commands/module-loadex
+        """
+        pieces = []
+        if options is not None:
+            pieces.append("CONFIG")
+            pieces.extend(options)
+        if args is not None:
+            pieces.append("ARGS")
+            pieces.extend(args)
+
+        return self.execute_command("MODULE LOADEX", path, *pieces)
+
     def module_unload(self, name) -> ResponseT:
         """
         Unloads the module ``name``.


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -4513,6 +4513,18 @@ def test_module(self, r):
             r.module_load("/some/fake/path", "arg1", "arg2", "arg3", "arg4")
             assert "Error loading the extension." in str(excinfo.value)
 
+    @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
+    @skip_if_redis_enterprise()
+    def test_module_loadex(self, r: redis.Redis):
+        with pytest.raises(redis.exceptions.ModuleError) as excinfo:
+            r.module_loadex("/some/fake/path")
+            assert "Error loading the extension." in str(excinfo.value)
+
+        with pytest.raises(redis.exceptions.ModuleError) as excinfo:
+            r.module_loadex("/some/fake/path", ["name", "value"], ["arg1", "arg2"])
+            assert "Error loading the extension." in str(excinfo.value)
+
     @skip_if_server_version_lt("2.6.0")
     def test_restore(self, r):
Repository: redis/redis-py

Issue:
Add support for LATENCY HISTOGRAM



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
@@ -1044,6 +1044,15 @@ def memory_purge(self, **kwargs) -> ResponseT:
         """
         return self.execute_command("MEMORY PURGE", **kwargs)
 
+    def latency_histogram(self, *args):
+        """
+        This function throws a NotImplementedError since it is intentionally
+        not supported.
+        """
+        raise NotImplementedError(
+            "LATENCY HISTOGRAM is intentionally not implemented in the client."
+        )
+
     def ping(self, **kwargs) -> ResponseT:
         """
         Ping the Redis server


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -4420,6 +4420,11 @@ def test_memory_usage(self, r):
         r.set("foo", "bar")
         assert isinstance(r.memory_usage("foo"), int)
 
+    @skip_if_server_version_lt("7.0.0")
+    def test_latency_histogram_not_implemented(self, r: redis.Redis):
+        with pytest.raises(NotImplementedError):
+            r.latency_histogram()
+
     @pytest.mark.onlynoncluster
     @skip_if_server_version_lt("4.0.0")
     @skip_if_redis_enterprise()
Repository: redis/redis-py

Issue:
v4.3.2 contains backwards incompatible change to `redis.lock.Lock`
**Version**: 4.3.2

**Platform**: Python 3.9 (applicable to all)

**Description**:  PR #2137 converted [several arguments](https://github.com/redis/redis-py/pull/2137/files#diff-2fde47ac5b94240803faba932c3e886043bcf9018470ecb4a218fd6533611914R80) to `redis.lock.Lock.__init__` into keyword only arguments. This is backwards incompatible as it disallows the use of these arguments positionally. Releasing this w/ 4.3.2 also violates semver 😅

The following snippet can be used to repro. It works fine on 4.3.1 but fails on 4.3.2 w/ a `TypeError`:

```python
from redis import Redis
from redis.lock import Lock

def main():
    r = Redis("localhost:6379")
    Lock(r, "schlage", None)

if __name__ == "__main__":
    main()
```

```
redis432 ❯ python nope.py
Traceback (most recent call last):
  File "nope.py", line 9, in <module>
    main()
  File "nope.py", line 6, in main
    Lock(r, "schlage", None)
TypeError: __init__() takes 3 positional arguments but 4 were given
```


Discussion:

Resolving patch:
diff --git a/redis/lock.py b/redis/lock.py
--- a/redis/lock.py
+++ b/redis/lock.py
@@ -77,7 +77,6 @@ def __init__(
         self,
         redis,
         name: str,
-        *,
         timeout: Optional[Number] = None,
         sleep: Number = 0.1,
         blocking: bool = True,
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
     long_description_content_type="text/markdown",
     keywords=["Redis", "key-value store", "database"],
     license="MIT",
-    version="4.3.2",
+    version="4.3.3",
     packages=find_packages(
         include=[
             "redis",


Test patch:
Repository: redis/redis-py

Issue:
xautoclaim discards first reply part (id of last scanned stream item)
**Version**: redis-py 4.3.3, redis 7.0.0

**Platform**: python 3.9, ubuntu 20.04

**Description**: I tried to implement the iterative XAUTOCLAIM as described in [Redis docs](https://redis.io/commands/xautoclaim/):

> The command returns the claimed entries as an array. It also returns a stream ID intended for cursor-like use as the <start> argument for its subsequent call. When there are no remaining PEL entries, the command returns the special 0-0 ID to signal completion.
> 
> ...
> 
> Return
>Array reply, specifically:
>An array with three elements:
> 1. A stream ID to be used as the <start> argument for the next call to XAUTOCLAIM.
> 2. An array containing all the successfully claimed messages in the same format as [XRANGE](https://redis.io/commands/xrange).
> 3. An array containing message IDs that no longer exist in the stream, and were deleted from the PEL in which they were found.

Unfortunately, I found out that redis-py drops first element of array completely and returns just the second part (claimed messages), as can be seen in [parse_xautoclaim](https://github.com/redis/redis-py/blob/master/redis/client.py#L310). So it is not possible to figure out the start_id for the next call.

What is the rationale behind that decision? It looks like there are cases when id of last scanned stream item is quite useful.



Discussion:

Resolving patch:
diff --git a/redis/client.py b/redis/client.py
--- a/redis/client.py
+++ b/redis/client.py
@@ -310,7 +310,8 @@ def parse_xclaim(response, **options):
 def parse_xautoclaim(response, **options):
     if options.get("parse_justid", False):
         return response[1]
-    return parse_stream_list(response[1])
+    response[1] = parse_stream_list(response[1])
+    return response
 
 
 def parse_xinfo_stream(response, **options):


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -3781,14 +3781,14 @@ def test_xautoclaim(self, r):
         # trying to claim a message that isn't already pending doesn't
         # do anything
         response = r.xautoclaim(stream, group, consumer2, min_idle_time=0)
-        assert response == []
+        assert response == [b"0-0", []]
 
         # read the group as consumer1 to initially claim the messages
         r.xreadgroup(group, consumer1, streams={stream: ">"})
 
         # claim one message as consumer2
         response = r.xautoclaim(stream, group, consumer2, min_idle_time=0, count=1)
-        assert response == [message]
+        assert response[1] == [message]
 
         # reclaim the messages as consumer1, but use the justid argument
         # which only returns message ids
Repository: redis/redis-py

Issue:
v4.3.3 continues to contain backwards incompatible change to ``redis.lock.Lock``
Hey this is a continuation of #2209.

The API change made in https://github.com/redis/redis-py/pull/2210/files does not restore backwards compatibility.

We are calling acquire like this:

```py
lock = redis.lock(
    "_lock{0}".format(key),
    timeout=self.lock_timeout,
    sleep=self.lock_sleep,
    thread_local=self.thread_local_lock,
)

lock.acquire(False)

```

this is broken by fa0be7671de6be85f859cbb57a31531b2482c9e1 which changes the API of lock.acquire() to use kw-only arguments.

since in #2209 it looks like py-redis has the willingness to revert these changes, hoping you can do that here also.  otherwise we have a dogpile.cache patch ready to go out to work around this.


Discussion:

Resolving patch:
diff --git a/redis/lock.py b/redis/lock.py
--- a/redis/lock.py
+++ b/redis/lock.py
@@ -169,7 +169,6 @@ def __exit__(
 
     def acquire(
         self,
-        *,
         sleep: Optional[Number] = None,
         blocking: Optional[bool] = None,
         blocking_timeout: Optional[Number] = None,


Test patch:
Repository: redis/redis-py

Issue:
`is_connected` does not return a boolean value
**Version**: 4.3.4

**Description**

The [`is_connected`](https://github.com/redis/redis-py/blob/4b0543d567aef36ac467ce495d831a24575d8d5b/redis/asyncio/connection.py#L687) property of the [`Connection`](https://github.com/redis/redis-py/blob/4b0543d567aef36ac467ce495d831a24575d8d5b/redis/asyncio/connection.py#L563) class in [`redis.asyncio.connection.py`](https://github.com/redis/redis-py/blob/4b0543d567aef36ac467ce495d831a24575d8d5b/redis/asyncio/connection.py) returns either `None`, a `StreamReader` or a `StreamWriter` rather than a boolean value as would be expected.

While this will work for all cases where the "truthiness" is checked directly, it could cause weird issues in other cases.

Such as logging the connection state, for example:
```python
# Prints: "Connection connected: <StreamWriter transport=<_ProactorSocketTransport fd=484 read=<_OverlappedFuture pending overlapped=<pending, 0x1666c1aa940> cb=[_ProactorReadPipeTransport._loop_reading()]>> reader=<StreamReader transport=<_ProactorSocketTransport fd=484 read=<_OverlappedFuture pending overlapped=<pending, 0x1666c1aa940> cb=[_ProactorReadPipeTransport._loop_reading()]>>>>"
print(f'Connection connected: {connection.is_connected}')
```



Discussion:

Resolving patch:
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -685,7 +685,7 @@ def __del__(self):
 
     @property
     def is_connected(self):
-        return self._reader and self._writer
+        return self._reader is not None and self._writer is not None
 
     def register_connect_callback(self, callback):
         self._connect_callbacks.append(weakref.WeakMethod(callback))


Test patch:
Repository: redis/redis-py

Issue:
Add support for TDIGEST.TRIMMED_MEAN
As of RedisBloom (master) 2.4.0, there is a new command TDIGEST.TRIMMED_MEAN.  We need to add support and the associated tests, as per [this PR](https://github.com/RedisBloom/RedisBloom/commit/15e10a139eb49a1af700d1c2853c32d18186b72b)


Discussion:

Resolving patch:
diff --git a/redis/commands/bf/__init__.py b/redis/commands/bf/__init__.py
--- a/redis/commands/bf/__init__.py
+++ b/redis/commands/bf/__init__.py
@@ -170,6 +170,7 @@ def __init__(self, client, **kwargs):
             TDIGEST_QUANTILE: parse_tdigest_quantile,
             TDIGEST_MIN: float,
             TDIGEST_MAX: float,
+            TDIGEST_TRIMMED_MEAN: float,
             TDIGEST_INFO: TDigestInfo,
         }
 
diff --git a/redis/commands/bf/commands.py b/redis/commands/bf/commands.py
--- a/redis/commands/bf/commands.py
+++ b/redis/commands/bf/commands.py
@@ -49,6 +49,7 @@
 TDIGEST_MIN = "TDIGEST.MIN"
 TDIGEST_MAX = "TDIGEST.MAX"
 TDIGEST_INFO = "TDIGEST.INFO"
+TDIGEST_TRIMMED_MEAN = "TDIGEST.TRIMMED_MEAN"
 TDIGEST_MERGESTORE = "TDIGEST.MERGESTORE"
 
 
@@ -418,6 +419,16 @@ def info(self, key):
         """  # noqa
         return self.execute_command(TDIGEST_INFO, key)
 
+    def trimmed_mean(self, key, low_cut_quantile, high_cut_quantile):
+        """
+        Return mean value from the sketch, excluding observation values outside
+        the low and high cutoff quantiles.
+        For more information see `TDIGEST.TRIMMED_MEAN <https://redis.io/commands/tdigest.trimmed_mean>`_.
+        """  # noqa
+        return self.execute_command(
+            TDIGEST_TRIMMED_MEAN, key, low_cut_quantile, high_cut_quantile
+        )
+
     def mergestore(self, dest_key, numkeys, *sourcekeys, compression=False):
         """
         Merges all of the values from `sourcekeys` keys to `dest_key` sketch.


Test patch:
diff --git a/tests/test_bloom.py b/tests/test_bloom.py
--- a/tests/test_bloom.py
+++ b/tests/test_bloom.py
@@ -388,6 +388,17 @@ def test_tdigest_cdf(client):
     assert 0.9 == round(client.tdigest().cdf("tDigest", 9.0), 1)
 
 
+@pytest.mark.redismod
+@pytest.mark.experimental
+@skip_ifmodversion_lt("2.4.0", "bf")
+def test_tdigest_trimmed_mean(client):
+    assert client.tdigest().create("tDigest", 100)
+    # insert data-points into sketch
+    assert client.tdigest().add("tDigest", list(range(1, 10)), [1.0] * 10)
+    assert 5 == client.tdigest().trimmed_mean("tDigest", 0.1, 0.9)
+    assert 4.5 == client.tdigest().trimmed_mean("tDigest", 0.4, 0.5)
+
+
 @pytest.mark.redismod
 @pytest.mark.experimental
 @skip_ifmodversion_lt("2.4.0", "bf")