Skip to content

Commit 012f7cf

Browse files
Add Redis.from_pool() class method, for explicitly owning and closing a ConnectionPool (#2913)
* Add the `from_pool` argument to asyncio.Redis * Add tests for the `from_pool` argument * Add a "from_pool" argument for the sync client too * Add automatic connection pool close for redis.sentinel * use from_pool() class method instead * re-add the auto_close_connection_pool arg to Connection.from_url() * Deprecate the "auto_close_connection_pool" argument to Redis() and Redis.from_url()
1 parent 086efb2 commit 012f7cf

File tree

9 files changed

+334
-50
lines changed

9 files changed

+334
-50
lines changed

docs/examples/asyncio_examples.ipynb

+28-5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@
4444
"await connection.close()"
4545
]
4646
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"If you create custom `ConnectionPool` for the `Redis` instance to use alone, use the `from_pool` class method to create it. This will cause the pool to be disconnected along with the Redis instance. Disconnecting the connection pool simply disconnects all connections hosted in the pool."
52+
]
53+
},
54+
{
55+
"cell_type": "code",
56+
"execution_count": null,
57+
"metadata": {},
58+
"outputs": [],
59+
"source": [
60+
"import redis.asyncio as redis\n",
61+
"\n",
62+
"pool = redis.ConnectionPool.from_url(\"redis://localhost\")\n",
63+
"connection = redis.Redis.from_pool(pool)\n",
64+
"await connection.close()"
65+
]
66+
},
4767
{
4868
"cell_type": "markdown",
4969
"metadata": {
@@ -53,7 +73,8 @@
5373
}
5474
},
5575
"source": [
56-
"If you supply a custom `ConnectionPool` that is supplied to several `Redis` instances, you may want to disconnect the connection pool explicitly. Disconnecting the connection pool simply disconnects all connections hosted in the pool."
76+
"\n",
77+
"However, If you supply a `ConnectionPool` that is shared several `Redis` instances, you may want to disconnect the connection pool explicitly. use the `connection_pool` argument in that case."
5778
]
5879
},
5980
{
@@ -69,10 +90,12 @@
6990
"source": [
7091
"import redis.asyncio as redis\n",
7192
"\n",
72-
"connection = redis.Redis(auto_close_connection_pool=False)\n",
73-
"await connection.close()\n",
74-
"# Or: await connection.close(close_connection_pool=False)\n",
75-
"await connection.connection_pool.disconnect()"
93+
"pool = redis.ConnectionPool.from_url(\"redis://localhost\")\n",
94+
"connection1 = redis.Redis(connection_pool=pool)\n",
95+
"connection2 = redis.Redis(connection_pool=pool)\n",
96+
"await connection1.close()\n",
97+
"await connection2.close()\n",
98+
"await pool.disconnect()"
7699
]
77100
},
78101
{

redis/asyncio/client.py

+53-12
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def from_url(
114114
cls,
115115
url: str,
116116
single_connection_client: bool = False,
117-
auto_close_connection_pool: bool = True,
117+
auto_close_connection_pool: Optional[bool] = None,
118118
**kwargs,
119119
):
120120
"""
@@ -160,12 +160,39 @@ class initializer. In the case of conflicting arguments, querystring
160160
161161
"""
162162
connection_pool = ConnectionPool.from_url(url, **kwargs)
163-
redis = cls(
163+
client = cls(
164164
connection_pool=connection_pool,
165165
single_connection_client=single_connection_client,
166166
)
167-
redis.auto_close_connection_pool = auto_close_connection_pool
168-
return redis
167+
if auto_close_connection_pool is not None:
168+
warnings.warn(
169+
DeprecationWarning(
170+
'"auto_close_connection_pool" is deprecated '
171+
"since version 5.0.0. "
172+
"Please create a ConnectionPool explicitly and "
173+
"provide to the Redis() constructor instead."
174+
)
175+
)
176+
else:
177+
auto_close_connection_pool = True
178+
client.auto_close_connection_pool = auto_close_connection_pool
179+
return client
180+
181+
@classmethod
182+
def from_pool(
183+
cls: Type["Redis"],
184+
connection_pool: ConnectionPool,
185+
) -> "Redis":
186+
"""
187+
Return a Redis client from the given connection pool.
188+
The Redis client will take ownership of the connection pool and
189+
close it when the Redis client is closed.
190+
"""
191+
client = cls(
192+
connection_pool=connection_pool,
193+
)
194+
client.auto_close_connection_pool = True
195+
return client
169196

170197
def __init__(
171198
self,
@@ -200,7 +227,8 @@ def __init__(
200227
lib_version: Optional[str] = get_lib_version(),
201228
username: Optional[str] = None,
202229
retry: Optional[Retry] = None,
203-
auto_close_connection_pool: bool = True,
230+
# deprecated. create a pool and use connection_pool instead
231+
auto_close_connection_pool: Optional[bool] = None,
204232
redis_connect_func=None,
205233
credential_provider: Optional[CredentialProvider] = None,
206234
protocol: Optional[int] = 2,
@@ -213,14 +241,21 @@ def __init__(
213241
To retry on TimeoutError, `retry_on_timeout` can also be set to `True`.
214242
"""
215243
kwargs: Dict[str, Any]
216-
# auto_close_connection_pool only has an effect if connection_pool is
217-
# None. This is a similar feature to the missing __del__ to resolve #1103,
218-
# but it accounts for whether a user wants to manually close the connection
219-
# pool, as a similar feature to ConnectionPool's __del__.
220-
self.auto_close_connection_pool = (
221-
auto_close_connection_pool if connection_pool is None else False
222-
)
244+
245+
if auto_close_connection_pool is not None:
246+
warnings.warn(
247+
DeprecationWarning(
248+
'"auto_close_connection_pool" is deprecated '
249+
"since version 5.0.0. "
250+
"Please create a ConnectionPool explicitly and "
251+
"provide to the Redis() constructor instead."
252+
)
253+
)
254+
else:
255+
auto_close_connection_pool = True
256+
223257
if not connection_pool:
258+
# Create internal connection pool, expected to be closed by Redis instance
224259
if not retry_on_error:
225260
retry_on_error = []
226261
if retry_on_timeout is True:
@@ -277,7 +312,13 @@ def __init__(
277312
"ssl_check_hostname": ssl_check_hostname,
278313
}
279314
)
315+
# This arg only used if no pool is passed in
316+
self.auto_close_connection_pool = auto_close_connection_pool
280317
connection_pool = ConnectionPool(**kwargs)
318+
else:
319+
# If a pool is passed in, do not close it
320+
self.auto_close_connection_pool = False
321+
281322
self.connection_pool = connection_pool
282323
self.single_connection_client = single_connection_client
283324
self.connection: Optional[Connection] = None

redis/asyncio/connection.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1106,8 +1106,8 @@ class BlockingConnectionPool(ConnectionPool):
11061106
"""
11071107
A blocking connection pool::
11081108
1109-
>>> from redis.asyncio.client import Redis
1110-
>>> client = Redis(connection_pool=BlockingConnectionPool())
1109+
>>> from redis.asyncio import Redis, BlockingConnectionPool
1110+
>>> client = Redis.from_pool(BlockingConnectionPool())
11111111
11121112
It performs the same function as the default
11131113
:py:class:`~redis.asyncio.ConnectionPool` implementation, in that,

redis/asyncio/sentinel.py

+2-12
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,7 @@ def master_for(
340340

341341
connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
342342
# The Redis object "owns" the pool
343-
auto_close_connection_pool = True
344-
client = redis_class(
345-
connection_pool=connection_pool,
346-
)
347-
client.auto_close_connection_pool = auto_close_connection_pool
348-
return client
343+
return redis_class.from_pool(connection_pool)
349344

350345
def slave_for(
351346
self,
@@ -377,9 +372,4 @@ def slave_for(
377372

378373
connection_pool = connection_pool_class(service_name, self, **connection_kwargs)
379374
# The Redis object "owns" the pool
380-
auto_close_connection_pool = True
381-
client = redis_class(
382-
connection_pool=connection_pool,
383-
)
384-
client.auto_close_connection_pool = auto_close_connection_pool
385-
return client
375+
return redis_class.from_pool(connection_pool)

redis/client.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import time
55
import warnings
66
from itertools import chain
7-
from typing import Optional
7+
from typing import Optional, Type
88

99
from redis._parsers.helpers import (
1010
_RedisCallbacks,
@@ -136,10 +136,28 @@ class initializer. In the case of conflicting arguments, querystring
136136
"""
137137
single_connection_client = kwargs.pop("single_connection_client", False)
138138
connection_pool = ConnectionPool.from_url(url, **kwargs)
139-
return cls(
139+
client = cls(
140140
connection_pool=connection_pool,
141141
single_connection_client=single_connection_client,
142142
)
143+
client.auto_close_connection_pool = True
144+
return client
145+
146+
@classmethod
147+
def from_pool(
148+
cls: Type["Redis"],
149+
connection_pool: ConnectionPool,
150+
) -> "Redis":
151+
"""
152+
Return a Redis client from the given connection pool.
153+
The Redis client will take ownership of the connection pool and
154+
close it when the Redis client is closed.
155+
"""
156+
client = cls(
157+
connection_pool=connection_pool,
158+
)
159+
client.auto_close_connection_pool = True
160+
return client
143161

144162
def __init__(
145163
self,
@@ -275,6 +293,10 @@ def __init__(
275293
}
276294
)
277295
connection_pool = ConnectionPool(**kwargs)
296+
self.auto_close_connection_pool = True
297+
else:
298+
self.auto_close_connection_pool = False
299+
278300
self.connection_pool = connection_pool
279301
self.connection = None
280302
if single_connection_client:
@@ -477,6 +499,9 @@ def close(self):
477499
self.connection = None
478500
self.connection_pool.release(conn)
479501

502+
if self.auto_close_connection_pool:
503+
self.connection_pool.disconnect()
504+
480505
def _send_command_parse_response(self, conn, command_name, *args, **options):
481506
"""
482507
Send a command and parse the response

redis/sentinel.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,8 @@ def master_for(
353353
kwargs["is_master"] = True
354354
connection_kwargs = dict(self.connection_kwargs)
355355
connection_kwargs.update(kwargs)
356-
return redis_class(
357-
connection_pool=connection_pool_class(
358-
service_name, self, **connection_kwargs
359-
)
356+
return redis_class.from_pool(
357+
connection_pool_class(service_name, self, **connection_kwargs)
360358
)
361359

362360
def slave_for(
@@ -386,8 +384,6 @@ def slave_for(
386384
kwargs["is_master"] = False
387385
connection_kwargs = dict(self.connection_kwargs)
388386
connection_kwargs.update(kwargs)
389-
return redis_class(
390-
connection_pool=connection_pool_class(
391-
service_name, self, **connection_kwargs
392-
)
387+
return redis_class.from_pool(
388+
connection_pool_class(service_name, self, **connection_kwargs)
393389
)

0 commit comments

Comments
 (0)