Repository: redis/redis-py
Pull request #2608 (2023-03-12 10:30:18)

Coverage for pypy-3.9
### Pull Request check-list

_Please make sure to review and check all of these items:_

- [ ] Does `$ tox` pass with this change (including linting)?
- [ ] Do the CI tests pass with this change (enable it first in your forked repo and wait for the github action build to finish)?
- [ ] Is the new or changed code fully tested?
- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
- [ ] Is there an example added to the examples folder (if applicable)?
- [ ] Was the change added to CHANGES file?

_NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open._

### Description of change

_Please provide a description of the change here._


codecov-commenter: ## [Codecov](https://codecov.io/gh/redis/redis-py/pull/2608?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis) Report
Patch coverage has no change and project coverage change: **`-14.10`** :warning:
> Comparison is base [(`6c708c2`)](https://codecov.io/gh/redis/redis-py/commit/6c708c2e0511364c2c3f21fa1259de05e590632d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis) 92.31% compared to head [(`66cf0b1`)](https://codecov.io/gh/redis/redis-py/pull/2608?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis) 78.22%.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis)

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           master    #2608       +/-   ##
===========================================
- Coverage   92.31%   78.22%   -14.10%     
===========================================
  Files         115      115               
  Lines       29744    29719       -25     
===========================================
- Hits        27459    23247     -4212     
- Misses       2285     6472     +4187     
```


[see 44 files with indirect coverage changes](https://codecov.io/gh/redis/redis-py/pull/2608/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis)

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/redis/redis-py/pull/2608?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redis).


Patch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index 8d38cd45..0f9db8fb 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -52,7 +52,7 @@ jobs:
      strategy:
        max-parallel: 15
        matrix:
-         python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7', 'pypy-3.8']
+         python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9']
          test-type: ['standalone', 'cluster']
          connection-type: ['hiredis', 'plain']
      env:
@@ -109,7 +109,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7']
+        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9']
     steps:
       - uses: actions/checkout@v3
       - uses: actions/setup-python@v4
diff --git a/CHANGES b/CHANGES
index e83660d6..3e4eba44 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,4 @@
+    * Use asyncio.timeout() instead of async_timeout.timeout() for python >= 3.11 (#2602)
     * Add test and fix async HiredisParser when reading during a disconnect() (#2349)
     * Use hiredis-py pack_command if available.
     * Support `.unlink()` in ClusterPipeline
diff --git a/README.md b/README.md
index 6b53b42f..e97119a8 100644
--- a/README.md
+++ b/README.md
@@ -13,12 +13,6 @@ The Python interface to the Redis key-value store.
 
 ---------------------------------------------
 
-## Python Notice
-
-redis-py 4.3.x will be the last generation of redis-py to support python 3.6 as it has been [End of Life'd](https://www.python.org/dev/peps/pep-0494/#schedule-last-security-only-release).  Async support was introduced in redis-py 4.2.x thanks to [aioredis](https://github.com/aio-libs/aioredis-py), which necessitates this change. We will continue to maintain 3.6 support as long as possible - but the plan is for redis-py version 4.4+ to officially remove 3.6.
-
----------------------------
-
 ## Installation
 
 Start a redis via docker:
@@ -42,6 +36,18 @@ $ pip install "redis[hiredis]"
 
 Looking for a high-level library to handle object mapping? See [redis-om-python](https://github.com/redis/redis-om-python)!
 
+## Supported Redis Versions
+
+The most recent version of this library supports redis version [5.0](https://github.com/redis/redis/blob/5.0/00-RELEASENOTES), [6.0](https://github.com/redis/redis/blob/6.0/00-RELEASENOTES), [6.2](https://github.com/redis/redis/blob/6.2/00-RELEASENOTES), and [7.0](https://github.com/redis/redis/blob/7.0/00-RELEASENOTES).
+
+The table below higlights version compatibility of the most-recent library versions and redis versions.
+
+| Library version | Supported redis versions |
+|-----------------|-------------------|
+| 3.5.3 | <= 6.2 Family of releases |
+| >= 4.1.0 | Version 5.0 to current |
+
+
 ## Usage
 
 ### Basic Example
diff --git a/docs/examples/connection_examples.ipynb b/docs/examples/connection_examples.ipynb
index a15b4c6c..7f5ac53e 100644
--- a/docs/examples/connection_examples.ipynb
+++ b/docs/examples/connection_examples.ipynb
@@ -222,18 +222,23 @@
     "import json\n",
     "import cachetools.func\n",
     "\n",
-    "sm_client = boto3.client('secretsmanager')\n",
-    "  \n",
-    "def sm_auth_provider(self, secret_id, version_id=None, version_stage='AWSCURRENT'):\n",
-    "    @cachetools.func.ttl_cache(maxsize=128, ttl=24 * 60 * 60) #24h\n",
-    "    def get_sm_user_credentials(secret_id, version_id, version_stage):\n",
-    "        secret = sm_client.get_secret_value(secret_id, version_id)\n",
-    "        return json.loads(secret['SecretString'])\n",
-    "    creds = get_sm_user_credentials(secret_id, version_id, version_stage)\n",
-    "    return creds['username'], creds['password']\n",
+    "class SecretsManagerProvider(redis.CredentialProvider):\n",
+    "    def __init__(self, secret_id, version_id=None, version_stage='AWSCURRENT'):\n",
+    "        self.sm_client = boto3.client('secretsmanager')\n",
+    "        self.secret_id = secret_id\n",
+    "        self.version_id = version_id\n",
+    "        self.version_stage = version_stage\n",
     "\n",
-    "secret_id = \"EXAMPLE1-90ab-cdef-fedc-ba987SECRET1\"\n",
-    "creds_provider = redis.CredentialProvider(supplier=sm_auth_provider, secret_id=secret_id)\n",
+    "    def get_credentials(self) -> Union[Tuple[str], Tuple[str, str]]:\n",
+    "        @cachetools.func.ttl_cache(maxsize=128, ttl=24 * 60 * 60) #24h\n",
+    "        def get_sm_user_credentials(secret_id, version_id, version_stage):\n",
+    "            secret = self.sm_client.get_secret_value(secret_id, version_id)\n",
+    "            return json.loads(secret['SecretString'])\n",
+    "        creds = get_sm_user_credentials(self.secret_id, self.version_id, self.version_stage)\n",
+    "        return creds['username'], creds['password']\n",
+    "\n",
+    "my_secret_id = \"EXAMPLE1-90ab-cdef-fedc-ba987SECRET1\"\n",
+    "creds_provider = SecretsManagerProvider(secret_id=my_secret_id)\n",
     "user_connection = redis.Redis(host=\"localhost\", port=6379, credential_provider=creds_provider)\n",
     "user_connection.ping()"
    ]
@@ -266,19 +271,24 @@
     "import boto3\n",
     "import cachetools.func\n",
     "\n",
-    "ec_client = boto3.client('elasticache')\n",
+    "class ElastiCacheIAMProvider(redis.CredentialProvider):\n",
+    "    def __init__(self, user, endpoint, port=6379, region=\"us-east-1\"):\n",
+    "        self.ec_client = boto3.client('elasticache')\n",
+    "        self.user = user\n",
+    "        self.endpoint = endpoint\n",
+    "        self.port = port\n",
+    "        self.region = region\n",
     "\n",
-    "def iam_auth_provider(self, user, endpoint, port=6379, region=\"us-east-1\"):\n",
-    "    @cachetools.func.ttl_cache(maxsize=128, ttl=15 * 60) # 15m\n",
-    "    def get_iam_auth_token(user, endpoint, port, region):\n",
-    "        return ec_client.generate_iam_auth_token(user, endpoint, port, region)\n",
-    "    iam_auth_token = get_iam_auth_token(endpoint, port, user, region)\n",
-    "    return iam_auth_token\n",
+    "    def get_credentials(self) -> Union[Tuple[str], Tuple[str, str]]:\n",
+    "        @cachetools.func.ttl_cache(maxsize=128, ttl=15 * 60) # 15m\n",
+    "        def get_iam_auth_token(user, endpoint, port, region):\n",
+    "            return self.ec_client.generate_iam_auth_token(user, endpoint, port, region)\n",
+    "        iam_auth_token = get_iam_auth_token(self.endpoint, self.port, self.user, self.region)\n",
+    "        return iam_auth_token\n",
     "\n",
     "username = \"barshaul\"\n",
     "endpoint = \"test-001.use1.cache.amazonaws.com\"\n",
-    "creds_provider = redis.CredentialProvider(supplier=iam_auth_provider, user=username,\n",
-    "                                           endpoint=endpoint)\n",
+    "creds_provider = ElastiCacheIAMProvider(user=username, endpoint=endpoint)\n",
     "user_connection = redis.Redis(host=endpoint, port=6379, credential_provider=creds_provider)\n",
     "user_connection.ping()"
    ]
diff --git a/docs/examples/ssl_connection_examples.ipynb b/docs/examples/ssl_connection_examples.ipynb
index 386e4af4..ab3b4415 100644
--- a/docs/examples/ssl_connection_examples.ipynb
+++ b/docs/examples/ssl_connection_examples.ipynb
@@ -55,6 +55,27 @@
     "url_connection.ping()"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "04e70233",
+   "metadata": {},
+   "source": [
+    "## Connecting to a Redis instance using ConnectionPool"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2903de26",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import redis\n",
+    "redis_pool = redis.ConnectionPool(host=\"localhost\", port=6666, connection_class=redis.SSLConnection)\n",
+    "ssl_connection = redis.StrictRedis(connection_pool=redis_pool) \n",
+    "ssl_connection.ping()"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
index e77fba30..93db37e4 100644
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -5,6 +5,7 @@ import inspect
 import os
 import socket
 import ssl
+import sys
 import threading
 import weakref
 from itertools import chain
@@ -24,7 +25,11 @@ from typing import (
 )
 from urllib.parse import ParseResult, parse_qs, unquote, urlparse
 
-import async_timeout
+if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
+    from asyncio import timeout as async_timeout
+else:
+    from async_timeout import timeout as async_timeout
+
 
 from redis.asyncio.retry import Retry
 from redis.backoff import NoBackoff
@@ -242,7 +247,7 @@ class PythonParser(BaseParser):
         if self._stream is None:
             raise RedisError("Buffer is closed.")
         try:
-            async with async_timeout.timeout(0):
+            async with async_timeout(0):
                 return await self._stream.read(1)
         except asyncio.TimeoutError:
             return False
@@ -267,9 +272,6 @@ class PythonParser(BaseParser):
         response: Any
         byte, response = raw[:1], raw[1:]
 
-        if byte not in (b"-", b"+", b":", b"$", b"*"):
-            raise InvalidResponse(f"Protocol Error: {raw!r}")
-
         # server returned an error
         if byte == b"-":
             response = response.decode("utf-8", errors="replace")
@@ -289,22 +291,24 @@ class PythonParser(BaseParser):
             pass
         # int value
         elif byte == b":":
-            response = int(response)
+            return int(response)
         # bulk response
+        elif byte == b"$" and response == b"-1":
+            return None
         elif byte == b"$":
-            length = int(response)
-            if length == -1:
-                return None
-            response = await self._read(length)
+            response = await self._read(int(response))
         # multi-bulk response
+        elif byte == b"*" and response == b"-1":
+            return None
         elif byte == b"*":
-            length = int(response)
-            if length == -1:
-                return None
             response = [
-                (await self._read_response(disable_decoding)) for _ in range(length)
+                (await self._read_response(disable_decoding))
+                for _ in range(int(response))  # noqa
             ]
-        if isinstance(response, bytes) and disable_decoding is False:
+        else:
+            raise InvalidResponse(f"Protocol Error: {raw!r}")
+
+        if disable_decoding is False:
             response = self.encoder.decode(response)
         return response
 
@@ -381,7 +385,7 @@ class HiredisParser(BaseParser):
         if self._reader.gets():
             return True
         try:
-            async with async_timeout.timeout(0):
+            async with async_timeout(0):
                 return await self.read_from_socket()
         except asyncio.TimeoutError:
             return False
@@ -636,7 +640,7 @@ class Connection:
 
     async def _connect(self):
         """Create a TCP socket connection"""
-        async with async_timeout.timeout(self.socket_connect_timeout):
+        async with async_timeout(self.socket_connect_timeout):
             reader, writer = await asyncio.open_connection(
                 host=self.host,
                 port=self.port,
@@ -723,7 +727,7 @@ class Connection:
     async def disconnect(self, nowait: bool = False) -> None:
         """Disconnects from the Redis server"""
         try:
-            async with async_timeout.timeout(self.socket_connect_timeout):
+            async with async_timeout(self.socket_connect_timeout):
                 self._parser.on_disconnect()
                 if not self.is_connected:
                     return
@@ -828,7 +832,7 @@ class Connection:
         read_timeout = timeout if timeout is not None else self.socket_timeout
         try:
             if read_timeout is not None:
-                async with async_timeout.timeout(read_timeout):
+                async with async_timeout(read_timeout):
                     response = await self._parser.read_response(
                         disable_decoding=disable_decoding
                     )
@@ -1119,7 +1123,7 @@ class UnixDomainSocketConnection(Connection):  # lgtm [py/missing-call-to-init]
         return pieces
 
     async def _connect(self):
-        async with async_timeout.timeout(self.socket_connect_timeout):
+        async with async_timeout(self.socket_connect_timeout):
             reader, writer = await asyncio.open_unix_connection(path=self.path)
         self._reader = reader
         self._writer = writer
@@ -1590,7 +1594,7 @@ class BlockingConnectionPool(ConnectionPool):
         # self.timeout then raise a ``ConnectionError``.
         connection = None
         try:
-            async with async_timeout.timeout(self.timeout):
+            async with async_timeout(self.timeout):
                 connection = await self.pool.get()
         except (asyncio.QueueEmpty, asyncio.TimeoutError):
             # Note that this is not caught by the redis client and will be
diff --git a/redis/cluster.py b/redis/cluster.py
index d6dc02d4..5e6e7da5 100644
--- a/redis/cluster.py
+++ b/redis/cluster.py
@@ -135,6 +135,7 @@ REDIS_ALLOWED_KEYS = (
     "redis_connect_func",
     "password",
     "port",
+    "queue_class",
     "retry",
     "retry_on_timeout",
     "socket_connect_timeout",
@@ -588,7 +589,6 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
         self.read_from_replicas = read_from_replicas
         self.reinitialize_counter = 0
         self.reinitialize_steps = reinitialize_steps
-        self.nodes_manager = None
         self.nodes_manager = NodesManager(
             startup_nodes=startup_nodes,
             from_url=from_url,
diff --git a/redis/commands/core.py b/redis/commands/core.py
index 28dab81f..e2cabb85 100644
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -3357,10 +3357,15 @@ class SetCommands(CommandsProtocol):
 
     def smismember(
         self, name: str, values: List, *args: List
-    ) -> Union[Awaitable[List[bool]], List[bool]]:
+    ) -> Union[
+        Awaitable[List[Union[Literal[0], Literal[1]]]],
+        List[Union[Literal[0], Literal[1]]],
+    ]:
         """
         Return whether each value in ``values`` is a member of the set ``name``
-        as a list of ``bool`` in the order of ``values``
+        as a list of ``int`` in the order of ``values``:
+        - 1 if the value is a member of the set.
+        - 0 if the value is not a member of the set or if key does not exist.
 
         For more information see https://redis.io/commands/smismember
         """
diff --git a/redis/commands/json/commands.py b/redis/commands/json/commands.py
index 7fd40392..c02c47ad 100644
--- a/redis/commands/json/commands.py
+++ b/redis/commands/json/commands.py
@@ -31,8 +31,8 @@ class JSONCommands:
         name: str,
         path: str,
         scalar: int,
-        start: Optional[int] = 0,
-        stop: Optional[int] = -1,
+        start: Optional[int] = None,
+        stop: Optional[int] = None,
     ) -> List[Union[int, None]]:
         """
         Return the index of ``scalar`` in the JSON array under ``path`` at key
@@ -43,9 +43,13 @@ class JSONCommands:
 
         For more information see `JSON.ARRINDEX <https://redis.io/commands/json.arrindex>`_.
         """  # noqa
-        return self.execute_command(
-            "JSON.ARRINDEX", name, str(path), self._encode(scalar), start, stop
-        )
+        pieces = [name, str(path), self._encode(scalar)]
+        if start is not None:
+            pieces.append(start)
+            if stop is not None:
+                pieces.append(stop)
+
+        return self.execute_command("JSON.ARRINDEX", *pieces)
 
     def arrinsert(
         self, name: str, path: str, index: int, *args: List[JsonType]
diff --git a/redis/commands/search/document.py b/redis/commands/search/document.py
index 5b305054..47534ec2 100644
--- a/redis/commands/search/document.py
+++ b/redis/commands/search/document.py
@@ -11,3 +11,7 @@ class Document:
 
     def __repr__(self):
         return f"Document {self.__dict__}"
+
+    def __getitem__(self, item):
+        value = getattr(self, item)
+        return value
diff --git a/redis/connection.py b/redis/connection.py
index d35980c1..c4a9685f 100644
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -358,9 +358,6 @@ class PythonParser(BaseParser):
 
         byte, response = raw[:1], raw[1:]
 
-        if byte not in (b"-", b"+", b":", b"$", b"*"):
-            raise InvalidResponse(f"Protocol Error: {raw!r}")
-
         # server returned an error
         if byte == b"-":
             response = response.decode("utf-8", errors="replace")
@@ -379,23 +376,24 @@ class PythonParser(BaseParser):
             pass
         # int value
         elif byte == b":":
-            response = int(response)
+            return int(response)
         # bulk response
+        elif byte == b"$" and response == b"-1":
+            return None
         elif byte == b"$":
-            length = int(response)
-            if length == -1:
-                return None
-            response = self._buffer.read(length)
+            response = self._buffer.read(int(response))
         # multi-bulk response
+        elif byte == b"*" and response == b"-1":
+            return None
         elif byte == b"*":
-            length = int(response)
-            if length == -1:
-                return None
             response = [
                 self._read_response(disable_decoding=disable_decoding)
-                for i in range(length)
+                for i in range(int(response))
             ]
-        if isinstance(response, bytes) and disable_decoding is False:
+        else:
+            raise InvalidResponse(f"Protocol Error: {raw!r}")
+
+        if disable_decoding is False:
             response = self.encoder.decode(response)
         return response
 
diff --git a/setup.py b/setup.py
index 060e9da7..ceeeb816 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,7 @@ setup(
     install_requires=[
         'importlib-metadata >= 1.0; python_version < "3.8"',
         'typing-extensions; python_version<"3.8"',
-        "async-timeout>=4.0.2",
+        'async-timeout>=4.0.2; python_version<"3.11"',
     ],
     classifiers=[
         "Development Status :: 5 - Production/Stable",


Test patch:
diff --git a/tests/test_asyncio/test_json.py b/tests/test_asyncio/test_json.py
index b8854d20..fc530c63 100644
--- a/tests/test_asyncio/test_json.py
+++ b/tests/test_asyncio/test_json.py
@@ -145,9 +145,15 @@ async def test_arrappend(modclient: redis.Redis):
 
 @pytest.mark.redismod
 async def test_arrindex(modclient: redis.Redis):
-    await modclient.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
-    assert 1 == await modclient.json().arrindex("arr", Path.root_path(), 1)
-    assert -1 == await modclient.json().arrindex("arr", Path.root_path(), 1, 2)
+    r_path = Path.root_path()
+    await modclient.json().set("arr", r_path, [0, 1, 2, 3, 4])
+    assert 1 == await modclient.json().arrindex("arr", r_path, 1)
+    assert -1 == await modclient.json().arrindex("arr", r_path, 1, 2)
+    assert 4 == await modclient.json().arrindex("arr", r_path, 4)
+    assert 4 == await modclient.json().arrindex("arr", r_path, 4, start=0)
+    assert 4 == await modclient.json().arrindex("arr", r_path, 4, start=0, stop=5000)
+    assert -1 == await modclient.json().arrindex("arr", r_path, 4, start=0, stop=-1)
+    assert -1 == await modclient.json().arrindex("arr", r_path, 4, start=1, stop=3)
 
 
 @pytest.mark.redismod
diff --git a/tests/test_asyncio/test_pubsub.py b/tests/test_asyncio/test_pubsub.py
index c2a9130e..0df7847e 100644
--- a/tests/test_asyncio/test_pubsub.py
+++ b/tests/test_asyncio/test_pubsub.py
@@ -5,7 +5,11 @@ import sys
 from typing import Optional
 from unittest.mock import patch
 
-import async_timeout
+if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
+    from asyncio import timeout as async_timeout
+else:
+    from async_timeout import timeout as async_timeout
+
 import pytest
 import pytest_asyncio
 
@@ -21,7 +25,7 @@ def with_timeout(t):
     def wrapper(corofunc):
         @functools.wraps(corofunc)
         async def run(*args, **kwargs):
-            async with async_timeout.timeout(t):
+            async with async_timeout(t):
                 return await corofunc(*args, **kwargs)
 
         return run
@@ -648,7 +652,7 @@ class TestPubSubReconnect:
 
         async def loop():
             # must make sure the task exits
-            async with async_timeout.timeout(2):
+            async with async_timeout(2):
                 nonlocal interrupt
                 await pubsub.subscribe("foo")
                 while True:
@@ -677,7 +681,7 @@ class TestPubSubReconnect:
 
         task = asyncio.get_running_loop().create_task(loop())
         # get the initial connect message
-        async with async_timeout.timeout(1):
+        async with async_timeout(1):
             message = await messages.get()
         assert message == {
             "channel": b"foo",
@@ -776,7 +780,7 @@ class TestPubSubRun:
             if n == 1:
                 break
             await asyncio.sleep(0.1)
-        async with async_timeout.timeout(0.1):
+        async with async_timeout(0.1):
             message = await messages.get()
         task.cancel()
         # we expect a cancelled error, not the Runtime error
@@ -839,7 +843,7 @@ class TestPubSubAutoReconnect:
         Test that a socket error will cause reconnect
         """
         try:
-            async with async_timeout.timeout(self.timeout):
+            async with async_timeout(self.timeout):
                 await self.mysetup(r, method)
                 # now, disconnect the connection, and wait for it to be re-established
                 async with self.cond:
@@ -868,7 +872,7 @@ class TestPubSubAutoReconnect:
         Test that a manual disconnect() will cause reconnect
         """
         try:
-            async with async_timeout.timeout(self.timeout):
+            async with async_timeout(self.timeout):
                 await self.mysetup(r, method)
                 # now, disconnect the connection, and wait for it to be re-established
                 async with self.cond:
@@ -923,7 +927,7 @@ class TestPubSubAutoReconnect:
     async def loop_step_listen(self):
         # get a single message via listen()
         try:
-            async with async_timeout.timeout(0.1):
+            async with async_timeout(0.1):
                 async for message in self.pubsub.listen():
                     await self.messages.put(message)
                     return True
@@ -947,7 +951,7 @@ class TestBaseException:
         assert pubsub.connection.is_connected
 
         async def get_msg_or_timeout(timeout=0.1):
-            async with async_timeout.timeout(timeout):
+            async with async_timeout(timeout):
                 # blocking method to return messages
                 while True:
                     response = await pubsub.parse_response(block=True)
diff --git a/tests/test_cluster.py b/tests/test_cluster.py
index 1bf57a35..58f9b77d 100644
--- a/tests/test_cluster.py
+++ b/tests/test_cluster.py
@@ -1,6 +1,7 @@
 import binascii
 import datetime
 import warnings
+from queue import LifoQueue, Queue
 from time import sleep
 from unittest.mock import DEFAULT, Mock, call, patch
 
@@ -2511,6 +2512,18 @@ class TestNodesManager:
                 node.redis_connection.connection_pool, connection_pool_class
             )
 
+    @pytest.mark.parametrize("queue_class", [Queue, LifoQueue])
+    def test_allow_custom_queue_class(self, queue_class):
+        rc = get_mocked_redis_client(
+            url="redis://my@DNS.com:7000",
+            cluster_slots=default_cluster_slots,
+            connection_pool_class=BlockingConnectionPool,
+            queue_class=queue_class,
+        )
+
+        for node in rc.nodes_manager.nodes_cache.values():
+            assert node.redis_connection.connection_pool.queue_class == queue_class
+
 
 @pytest.mark.onlycluster
 class TestClusterPubSubObject:
diff --git a/tests/test_json.py b/tests/test_json.py
index a776e9e7..8e8da056 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -166,6 +166,11 @@ def test_arrindex(client):
     client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
     assert 1 == client.json().arrindex("arr", Path.root_path(), 1)
     assert -1 == client.json().arrindex("arr", Path.root_path(), 1, 2)
+    assert 4 == client.json().arrindex("arr", Path.root_path(), 4)
+    assert 4 == client.json().arrindex("arr", Path.root_path(), 4, start=0)
+    assert 4 == client.json().arrindex("arr", Path.root_path(), 4, start=0, stop=5000)
+    assert -1 == client.json().arrindex("arr", Path.root_path(), 4, start=0, stop=-1)
+    assert -1 == client.json().arrindex("arr", Path.root_path(), 4, start=1, stop=3)
 
 
 @pytest.mark.redismod
diff --git a/tests/test_search.py b/tests/test_search.py
index 57d4338e..7a242815 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -140,7 +140,9 @@ def test_client(client):
 
     for doc in res.docs:
         assert doc.id
+        assert doc["id"]
         assert doc.play == "Henry IV"
+        assert doc["play"] == "Henry IV"
         assert len(doc.txt) > 0
 
     # test no content