Repository: redis/redis-py

Issue:
Instantiating BlockingConnectionPool.from_url with timeout in query args fails
**Version**: 5.0.1
**Platform**: All
**Description**: Missing cast in URL_QUERY_ARGUMENT_PARSERS for "timeout" parameter causes a runtime TypeError

When instantiating a BlockingConnectionPool from url with timeout query parameter, timeout is not cast to float, resulting in timeout to be set to string in BlockingConnectionPool.__init__.

Later on when attempting to use the BlockingConnectionPool, comparison of timeout (as a string) to an int causes a   `TypeError '<' not supported between instances of 'str' and 'int'`

TestCase:

```python
>>> from redis.connection import BlockingConnectionPool
>>> pool = BlockingConnectionPool.from_url("redis://127.0.0.1:6379/1?timeout=42")
>>> pool.timeout
'42'
>>> pool.get_connection("PING")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/r0ro/.virtualenvs/backoffice/lib/python3.10/site-packages/redis/connection.py", line 1262, in get_connection
    connection = self.pool.get(block=True, timeout=self.timeout)
  File "/usr/lib/python3.10/queue.py", line 172, in get
    elif timeout < 0:
TypeError: '<' not supported between instances of 'str' and 'int'
```





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
@@ -861,6 +861,7 @@ def to_bool(value) -> Optional[bool]:
         "max_connections": int,
         "health_check_interval": int,
         "ssl_check_hostname": to_bool,
+        "timeout": float,
     }
 )
 
diff --git a/redis/connection.py b/redis/connection.py
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -853,6 +853,7 @@ def to_bool(value):
     "max_connections": int,
     "health_check_interval": int,
     "ssl_check_hostname": to_bool,
+    "timeout": float,
 }
 
 


Test patch:
diff --git a/tests/test_asyncio/test_connection_pool.py b/tests/test_asyncio/test_connection_pool.py
--- a/tests/test_asyncio/test_connection_pool.py
+++ b/tests/test_asyncio/test_connection_pool.py
@@ -454,6 +454,31 @@ def test_invalid_scheme_raises_error(self):
         )
 
 
+class TestBlockingConnectionPoolURLParsing:
+    def test_extra_typed_querystring_options(self):
+        pool = redis.BlockingConnectionPool.from_url(
+            "redis://localhost/2?socket_timeout=20&socket_connect_timeout=10"
+            "&socket_keepalive=&retry_on_timeout=Yes&max_connections=10&timeout=13.37"
+        )
+
+        assert pool.connection_class == redis.Connection
+        assert pool.connection_kwargs == {
+            "host": "localhost",
+            "db": 2,
+            "socket_timeout": 20.0,
+            "socket_connect_timeout": 10.0,
+            "retry_on_timeout": True,
+        }
+        assert pool.max_connections == 10
+        assert pool.timeout == 13.37
+
+    def test_invalid_extra_typed_querystring_options(self):
+        with pytest.raises(ValueError):
+            redis.BlockingConnectionPool.from_url(
+                "redis://localhost/2?timeout=_not_a_float_"
+            )
+
+
 class TestConnectionPoolUnixSocketURLParsing:
     def test_defaults(self):
         pool = redis.ConnectionPool.from_url("unix:///socket")
diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py
--- a/tests/test_connection_pool.py
+++ b/tests/test_connection_pool.py
@@ -359,6 +359,31 @@ def test_invalid_scheme_raises_error_when_double_slash_missing(self):
         )
 
 
+class TestBlockingConnectionPoolURLParsing:
+    def test_extra_typed_querystring_options(self):
+        pool = redis.BlockingConnectionPool.from_url(
+            "redis://localhost/2?socket_timeout=20&socket_connect_timeout=10"
+            "&socket_keepalive=&retry_on_timeout=Yes&max_connections=10&timeout=42"
+        )
+
+        assert pool.connection_class == redis.Connection
+        assert pool.connection_kwargs == {
+            "host": "localhost",
+            "db": 2,
+            "socket_timeout": 20.0,
+            "socket_connect_timeout": 10.0,
+            "retry_on_timeout": True,
+        }
+        assert pool.max_connections == 10
+        assert pool.timeout == 42.0
+
+    def test_invalid_extra_typed_querystring_options(self):
+        with pytest.raises(ValueError):
+            redis.BlockingConnectionPool.from_url(
+                "redis://localhost/2?timeout=_not_a_float_"
+            )
+
+
 class TestConnectionPoolUnixSocketURLParsing:
     def test_defaults(self):
         pool = redis.ConnectionPool.from_url("unix:///socket")