coredis 5.5.0__cp314-cp314-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 22fe76227e35f92ab5c3__mypyc.cpython-314-darwin.so +0 -0
- coredis/__init__.py +42 -0
- coredis/_enum.py +42 -0
- coredis/_json.py +11 -0
- coredis/_packer.cpython-314-darwin.so +0 -0
- coredis/_packer.py +71 -0
- coredis/_protocols.py +50 -0
- coredis/_py_311_typing.py +20 -0
- coredis/_py_312_typing.py +17 -0
- coredis/_sidecar.py +114 -0
- coredis/_utils.cpython-314-darwin.so +0 -0
- coredis/_utils.py +440 -0
- coredis/_version.py +34 -0
- coredis/_version.pyi +1 -0
- coredis/cache.py +801 -0
- coredis/client/__init__.py +6 -0
- coredis/client/basic.py +1240 -0
- coredis/client/cluster.py +1265 -0
- coredis/commands/__init__.py +64 -0
- coredis/commands/_key_spec.py +517 -0
- coredis/commands/_utils.py +108 -0
- coredis/commands/_validators.py +159 -0
- coredis/commands/_wrappers.py +175 -0
- coredis/commands/bitfield.py +110 -0
- coredis/commands/constants.py +662 -0
- coredis/commands/core.py +8484 -0
- coredis/commands/function.py +408 -0
- coredis/commands/monitor.py +168 -0
- coredis/commands/pubsub.py +905 -0
- coredis/commands/request.py +108 -0
- coredis/commands/script.py +296 -0
- coredis/commands/sentinel.py +246 -0
- coredis/config.py +50 -0
- coredis/connection.py +906 -0
- coredis/constants.cpython-314-darwin.so +0 -0
- coredis/constants.py +37 -0
- coredis/credentials.py +45 -0
- coredis/exceptions.py +360 -0
- coredis/experimental/__init__.py +1 -0
- coredis/globals.py +23 -0
- coredis/modules/__init__.py +121 -0
- coredis/modules/autocomplete.py +138 -0
- coredis/modules/base.py +262 -0
- coredis/modules/filters.py +1319 -0
- coredis/modules/graph.py +362 -0
- coredis/modules/json.py +691 -0
- coredis/modules/response/__init__.py +0 -0
- coredis/modules/response/_callbacks/__init__.py +0 -0
- coredis/modules/response/_callbacks/autocomplete.py +42 -0
- coredis/modules/response/_callbacks/graph.py +237 -0
- coredis/modules/response/_callbacks/json.py +21 -0
- coredis/modules/response/_callbacks/search.py +221 -0
- coredis/modules/response/_callbacks/timeseries.py +158 -0
- coredis/modules/response/types.py +179 -0
- coredis/modules/search.py +1089 -0
- coredis/modules/timeseries.py +1139 -0
- coredis/parser.cpython-314-darwin.so +0 -0
- coredis/parser.py +344 -0
- coredis/pipeline.py +1225 -0
- coredis/pool/__init__.py +11 -0
- coredis/pool/basic.py +453 -0
- coredis/pool/cluster.py +517 -0
- coredis/pool/nodemanager.py +340 -0
- coredis/py.typed +0 -0
- coredis/recipes/__init__.py +0 -0
- coredis/recipes/credentials/__init__.py +5 -0
- coredis/recipes/credentials/iam_provider.py +63 -0
- coredis/recipes/locks/__init__.py +5 -0
- coredis/recipes/locks/extend.lua +17 -0
- coredis/recipes/locks/lua_lock.py +281 -0
- coredis/recipes/locks/release.lua +10 -0
- coredis/response/__init__.py +5 -0
- coredis/response/_callbacks/__init__.py +538 -0
- coredis/response/_callbacks/acl.py +32 -0
- coredis/response/_callbacks/cluster.py +183 -0
- coredis/response/_callbacks/command.py +86 -0
- coredis/response/_callbacks/connection.py +31 -0
- coredis/response/_callbacks/geo.py +58 -0
- coredis/response/_callbacks/hash.py +85 -0
- coredis/response/_callbacks/keys.py +59 -0
- coredis/response/_callbacks/module.py +33 -0
- coredis/response/_callbacks/script.py +85 -0
- coredis/response/_callbacks/sentinel.py +179 -0
- coredis/response/_callbacks/server.py +241 -0
- coredis/response/_callbacks/sets.py +44 -0
- coredis/response/_callbacks/sorted_set.py +204 -0
- coredis/response/_callbacks/streams.py +185 -0
- coredis/response/_callbacks/strings.py +70 -0
- coredis/response/_callbacks/vector_sets.py +159 -0
- coredis/response/_utils.py +33 -0
- coredis/response/types.py +416 -0
- coredis/retry.py +233 -0
- coredis/sentinel.py +477 -0
- coredis/stream.py +369 -0
- coredis/tokens.py +2286 -0
- coredis/typing.py +593 -0
- coredis-5.5.0.dist-info/METADATA +211 -0
- coredis-5.5.0.dist-info/RECORD +100 -0
- coredis-5.5.0.dist-info/WHEEL +6 -0
- coredis-5.5.0.dist-info/licenses/LICENSE +23 -0
coredis/pool/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .basic import BlockingConnectionPool, ConnectionPool
|
|
4
|
+
from .cluster import BlockingClusterConnectionPool, ClusterConnectionPool
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"ConnectionPool",
|
|
8
|
+
"BlockingConnectionPool",
|
|
9
|
+
"ClusterConnectionPool",
|
|
10
|
+
"BlockingClusterConnectionPool",
|
|
11
|
+
]
|
coredis/pool/basic.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import warnings
|
|
8
|
+
from itertools import chain
|
|
9
|
+
from ssl import SSLContext, VerifyMode
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
from urllib.parse import parse_qs, unquote, urlparse
|
|
12
|
+
|
|
13
|
+
import async_timeout
|
|
14
|
+
|
|
15
|
+
from coredis._utils import query_param_to_bool
|
|
16
|
+
from coredis.connection import (
|
|
17
|
+
BaseConnection,
|
|
18
|
+
Connection,
|
|
19
|
+
RedisSSLContext,
|
|
20
|
+
UnixDomainSocketConnection,
|
|
21
|
+
)
|
|
22
|
+
from coredis.exceptions import ConnectionError
|
|
23
|
+
from coredis.typing import Callable, ClassVar, RedisValueT, TypeVar
|
|
24
|
+
|
|
25
|
+
_CPT = TypeVar("_CPT", bound="ConnectionPool")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConnectionPool:
|
|
29
|
+
"""Generic connection pool"""
|
|
30
|
+
|
|
31
|
+
#: Mapping of querystring arguments to their parser functions
|
|
32
|
+
URL_QUERY_ARGUMENT_PARSERS: ClassVar[
|
|
33
|
+
dict[str, Callable[..., int | float | bool | str | None]]
|
|
34
|
+
] = {
|
|
35
|
+
"client_name": str,
|
|
36
|
+
"stream_timeout": float,
|
|
37
|
+
"connect_timeout": float,
|
|
38
|
+
"max_connections": int,
|
|
39
|
+
"max_idle_time": int,
|
|
40
|
+
"protocol_version": int,
|
|
41
|
+
"idle_check_interval": int,
|
|
42
|
+
"noreply": bool,
|
|
43
|
+
"noevict": bool,
|
|
44
|
+
"notouch": bool,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_url(
|
|
49
|
+
cls: type[_CPT],
|
|
50
|
+
url: str,
|
|
51
|
+
db: int | None = None,
|
|
52
|
+
decode_components: bool = False,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> _CPT:
|
|
55
|
+
"""
|
|
56
|
+
Returns a connection pool configured from the given URL.
|
|
57
|
+
|
|
58
|
+
For example:
|
|
59
|
+
|
|
60
|
+
- ``redis://[:password]@localhost:6379/0``
|
|
61
|
+
- ``rediss://[:password]@localhost:6379/0``
|
|
62
|
+
- ``unix://[:password]@/path/to/socket.sock?db=0``
|
|
63
|
+
|
|
64
|
+
Three URL schemes are supported:
|
|
65
|
+
|
|
66
|
+
- `redis:// <http://www.iana.org/assignments/uri-schemes/prov/redis>`__
|
|
67
|
+
creates a normal TCP socket connection
|
|
68
|
+
- `rediss:// <http://www.iana.org/assignments/uri-schemes/prov/rediss>`__
|
|
69
|
+
creates a SSL wrapped TCP socket connection
|
|
70
|
+
- ``unix://`` creates a Unix Domain Socket connection
|
|
71
|
+
|
|
72
|
+
There are several ways to specify a database number. The parse function
|
|
73
|
+
will return the first specified option:
|
|
74
|
+
|
|
75
|
+
- A ``db`` querystring option, e.g. ``redis://localhost?db=0``
|
|
76
|
+
- If using the ``redis://`` scheme, the path argument of the url, e.g.
|
|
77
|
+
``redis://localhost/0``
|
|
78
|
+
- The ``db`` argument to this function.
|
|
79
|
+
|
|
80
|
+
If none of these options are specified, ``db=0`` is used.
|
|
81
|
+
|
|
82
|
+
The :paramref:`decode_components` argument allows this function to work with
|
|
83
|
+
percent-encoded URLs. If this argument is set to ``True`` all ``%xx``
|
|
84
|
+
escapes will be replaced by their single-character equivalents after
|
|
85
|
+
the URL has been parsed. This only applies to the ``hostname``,
|
|
86
|
+
``path``, and ``password`` components. See :attr:`URL_QUERY_ARGUMENT_PARSERS`
|
|
87
|
+
for a comprehensive mapping of querystring parameters to how they are parsed.
|
|
88
|
+
|
|
89
|
+
Any additional querystring arguments and keyword arguments will be
|
|
90
|
+
passed along to the class constructor. The querystring
|
|
91
|
+
arguments ``connect_timeout`` and ``stream_timeout`` if supplied
|
|
92
|
+
are parsed as float values.
|
|
93
|
+
|
|
94
|
+
.. note:: In the case of conflicting arguments, querystring arguments always win.
|
|
95
|
+
"""
|
|
96
|
+
parsed_url = urlparse(url)
|
|
97
|
+
qs = parsed_url.query
|
|
98
|
+
|
|
99
|
+
url_options: dict[
|
|
100
|
+
str,
|
|
101
|
+
int | float | bool | str | type[BaseConnection] | SSLContext | None,
|
|
102
|
+
] = {}
|
|
103
|
+
for name, value in iter(parse_qs(qs).items()):
|
|
104
|
+
if value and len(value) > 0:
|
|
105
|
+
parser = cls.URL_QUERY_ARGUMENT_PARSERS.get(name)
|
|
106
|
+
|
|
107
|
+
if parser:
|
|
108
|
+
try:
|
|
109
|
+
url_options[name] = parser(value[0])
|
|
110
|
+
except (TypeError, ValueError):
|
|
111
|
+
warnings.warn(UserWarning(f"Invalid value for `{name}` in connection URL."))
|
|
112
|
+
else:
|
|
113
|
+
url_options[name] = value[0]
|
|
114
|
+
|
|
115
|
+
username: str | None = parsed_url.username
|
|
116
|
+
password: str | None = parsed_url.password
|
|
117
|
+
path: str | None = parsed_url.path
|
|
118
|
+
hostname: str | None = parsed_url.hostname
|
|
119
|
+
|
|
120
|
+
if decode_components:
|
|
121
|
+
username = unquote(username) if username else None
|
|
122
|
+
password = unquote(password) if password else None
|
|
123
|
+
path = unquote(path) if path else None
|
|
124
|
+
hostname = unquote(hostname) if hostname else None
|
|
125
|
+
|
|
126
|
+
# We only support redis:// and unix:// schemes.
|
|
127
|
+
|
|
128
|
+
if parsed_url.scheme == "unix":
|
|
129
|
+
url_options.update(
|
|
130
|
+
{
|
|
131
|
+
"username": username,
|
|
132
|
+
"password": password,
|
|
133
|
+
"path": path,
|
|
134
|
+
"connection_class": UnixDomainSocketConnection,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
url_options.update(
|
|
140
|
+
{
|
|
141
|
+
"host": hostname,
|
|
142
|
+
"port": int(parsed_url.port or 6379),
|
|
143
|
+
"username": username,
|
|
144
|
+
"password": password,
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# If there's a path argument, use it as the db argument if a
|
|
149
|
+
# querystring value wasn't specified
|
|
150
|
+
|
|
151
|
+
if "db" not in url_options and path:
|
|
152
|
+
try:
|
|
153
|
+
url_options["db"] = int(path.replace("/", ""))
|
|
154
|
+
except (AttributeError, ValueError):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
if parsed_url.scheme == "rediss":
|
|
158
|
+
if "ssl_context" not in kwargs:
|
|
159
|
+
keyfile = cast(str | None, url_options.pop("ssl_keyfile", None))
|
|
160
|
+
certfile = cast(str | None, url_options.pop("ssl_certfile", None))
|
|
161
|
+
check_hostname = query_param_to_bool(
|
|
162
|
+
url_options.pop("ssl_check_hostname", None)
|
|
163
|
+
)
|
|
164
|
+
cert_reqs = cast(
|
|
165
|
+
str | VerifyMode | None,
|
|
166
|
+
url_options.pop("ssl_cert_reqs", None),
|
|
167
|
+
)
|
|
168
|
+
ca_certs = cast(str | None, url_options.pop("ssl_ca_certs", None))
|
|
169
|
+
url_options["ssl_context"] = RedisSSLContext(
|
|
170
|
+
keyfile, certfile, cert_reqs, ca_certs, check_hostname
|
|
171
|
+
).get()
|
|
172
|
+
|
|
173
|
+
# last shot at the db value
|
|
174
|
+
_db = url_options.get("db", db or 0)
|
|
175
|
+
assert isinstance(_db, (int, str, bytes))
|
|
176
|
+
url_options["db"] = int(_db)
|
|
177
|
+
|
|
178
|
+
# update the arguments from the URL values
|
|
179
|
+
kwargs.update(url_options)
|
|
180
|
+
|
|
181
|
+
return cls(**kwargs)
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self,
|
|
185
|
+
*,
|
|
186
|
+
connection_class: type[Connection] | None = None,
|
|
187
|
+
max_connections: int | None = None,
|
|
188
|
+
max_idle_time: int = 0,
|
|
189
|
+
idle_check_interval: int = 1,
|
|
190
|
+
**connection_kwargs: Any | None,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Creates a connection pool. If :paramref:`max_connections` is set, then this
|
|
194
|
+
object raises :class:`~coredis.ConnectionError` when the pool's limit is reached.
|
|
195
|
+
|
|
196
|
+
By default, TCP connections are created :paramref:`connection_class` is specified.
|
|
197
|
+
Use :class:`~coredis.UnixDomainSocketConnection` for unix sockets.
|
|
198
|
+
|
|
199
|
+
Any additional keyword arguments are passed to the constructor of
|
|
200
|
+
connection_class.
|
|
201
|
+
"""
|
|
202
|
+
self.connection_class = connection_class or Connection
|
|
203
|
+
self.connection_kwargs = connection_kwargs
|
|
204
|
+
self.max_connections = max_connections or 2**31
|
|
205
|
+
self.max_idle_time = max_idle_time
|
|
206
|
+
self.idle_check_interval = idle_check_interval
|
|
207
|
+
self.initialized = False
|
|
208
|
+
self.reset()
|
|
209
|
+
self.decode_responses = bool(self.connection_kwargs.get("decode_responses", False))
|
|
210
|
+
self.encoding = str(self.connection_kwargs.get("encoding", "utf-8"))
|
|
211
|
+
|
|
212
|
+
async def initialize(self) -> None:
|
|
213
|
+
self.initialized = True
|
|
214
|
+
|
|
215
|
+
def __repr__(self) -> str:
|
|
216
|
+
return f"{type(self).__name__}<{self.connection_class.describe(self.connection_kwargs)}>"
|
|
217
|
+
|
|
218
|
+
def __del__(self) -> None:
|
|
219
|
+
self.disconnect()
|
|
220
|
+
|
|
221
|
+
async def disconnect_on_idle_time_exceeded(self, connection: Connection) -> None:
|
|
222
|
+
while True:
|
|
223
|
+
if (
|
|
224
|
+
time.time() - connection.last_active_at > self.max_idle_time
|
|
225
|
+
and not connection.requests_pending
|
|
226
|
+
):
|
|
227
|
+
connection.disconnect()
|
|
228
|
+
if connection in self._available_connections:
|
|
229
|
+
self._available_connections.remove(connection)
|
|
230
|
+
self._created_connections -= 1
|
|
231
|
+
break
|
|
232
|
+
await asyncio.sleep(self.idle_check_interval)
|
|
233
|
+
|
|
234
|
+
def reset(self) -> None:
|
|
235
|
+
self.pid = os.getpid()
|
|
236
|
+
self._created_connections = 0
|
|
237
|
+
self._available_connections: list[Connection] = []
|
|
238
|
+
self._in_use_connections: set[Connection] = set()
|
|
239
|
+
self._check_lock = threading.Lock()
|
|
240
|
+
|
|
241
|
+
def checkpid(self) -> None: # noqa
|
|
242
|
+
if self.pid != os.getpid():
|
|
243
|
+
with self._check_lock:
|
|
244
|
+
# Double check
|
|
245
|
+
if self.pid == os.getpid():
|
|
246
|
+
return
|
|
247
|
+
self.disconnect()
|
|
248
|
+
self.reset()
|
|
249
|
+
|
|
250
|
+
def peek_available(self) -> BaseConnection | None:
|
|
251
|
+
return self._available_connections[0] if self._available_connections else None
|
|
252
|
+
|
|
253
|
+
async def get_connection(
|
|
254
|
+
self,
|
|
255
|
+
command_name: bytes | None = None,
|
|
256
|
+
*args: RedisValueT,
|
|
257
|
+
acquire: bool = True,
|
|
258
|
+
**kwargs: RedisValueT | None,
|
|
259
|
+
) -> Connection:
|
|
260
|
+
"""Gets a connection from the pool"""
|
|
261
|
+
self.checkpid()
|
|
262
|
+
try:
|
|
263
|
+
connection = self._available_connections.pop()
|
|
264
|
+
if connection.is_connected and connection.needs_handshake:
|
|
265
|
+
await connection.perform_handshake()
|
|
266
|
+
except IndexError:
|
|
267
|
+
if self._created_connections >= self.max_connections:
|
|
268
|
+
raise ConnectionError("Too many connections")
|
|
269
|
+
connection = self._make_connection(**kwargs)
|
|
270
|
+
|
|
271
|
+
if acquire:
|
|
272
|
+
self._in_use_connections.add(connection)
|
|
273
|
+
else:
|
|
274
|
+
self._available_connections.append(connection)
|
|
275
|
+
|
|
276
|
+
return connection
|
|
277
|
+
|
|
278
|
+
def release(self, connection: Connection) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Releases the :paramref:`connection` back to the pool
|
|
281
|
+
"""
|
|
282
|
+
self.checkpid()
|
|
283
|
+
|
|
284
|
+
if connection.pid == self.pid:
|
|
285
|
+
self._in_use_connections.remove(connection)
|
|
286
|
+
self._available_connections.append(connection)
|
|
287
|
+
|
|
288
|
+
def disconnect(self) -> None:
|
|
289
|
+
"""Closes all connections in the pool"""
|
|
290
|
+
all_conns = chain(self._available_connections, self._in_use_connections)
|
|
291
|
+
|
|
292
|
+
for connection in all_conns:
|
|
293
|
+
connection.disconnect()
|
|
294
|
+
self._created_connections -= 1
|
|
295
|
+
|
|
296
|
+
def _make_connection(self, **options: RedisValueT | None) -> Connection:
|
|
297
|
+
"""
|
|
298
|
+
Creates a new connection
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
self._created_connections += 1
|
|
302
|
+
connection = self.connection_class(
|
|
303
|
+
**self.connection_kwargs, # type: ignore
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if self.max_idle_time > self.idle_check_interval > 0:
|
|
307
|
+
# do not await the future
|
|
308
|
+
asyncio.ensure_future(self.disconnect_on_idle_time_exceeded(connection))
|
|
309
|
+
|
|
310
|
+
return connection
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class BlockingConnectionPool(ConnectionPool):
|
|
314
|
+
"""
|
|
315
|
+
Blocking connection pool::
|
|
316
|
+
|
|
317
|
+
>>> from coredis import Redis
|
|
318
|
+
>>> client = Redis(connection_pool=BlockingConnectionPool())
|
|
319
|
+
|
|
320
|
+
It performs the same function as the default
|
|
321
|
+
:class:`~coredis.ConnectionPool`, in that, it maintains a pool of reusable
|
|
322
|
+
connections that can be shared by multiple redis clients.
|
|
323
|
+
|
|
324
|
+
The difference is that, in the event that a client tries to get a
|
|
325
|
+
connection from the pool when all of the connections are in use, rather than
|
|
326
|
+
raising a :exc:`~coredis.ConnectionError` (as the default
|
|
327
|
+
:class:`~coredis.ConnectionPool` implementation does), it
|
|
328
|
+
makes the client blocks for a specified number of seconds until
|
|
329
|
+
a connection becomes available.
|
|
330
|
+
|
|
331
|
+
Use :paramref:`max_connections` to increase / decrease the pool size::
|
|
332
|
+
|
|
333
|
+
>>> pool = BlockingConnectionPool(max_connections=10)
|
|
334
|
+
|
|
335
|
+
Use :paramref:`timeout` to tell it either how many seconds to wait for a
|
|
336
|
+
connection to become available, or to block forever::
|
|
337
|
+
|
|
338
|
+
>>> # Block forever.
|
|
339
|
+
>>> pool = BlockingConnectionPool(timeout=None)
|
|
340
|
+
>>> # Raise a ``ConnectionError`` after five seconds if a connection is
|
|
341
|
+
>>> # not available.
|
|
342
|
+
>>> pool = BlockingConnectionPool(timeout=5)
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
connection_class: type[Connection] | None = None,
|
|
348
|
+
queue_class: type[asyncio.Queue[Connection | None]] = asyncio.LifoQueue,
|
|
349
|
+
max_connections: int | None = None,
|
|
350
|
+
timeout: int = 20,
|
|
351
|
+
max_idle_time: int = 0,
|
|
352
|
+
idle_check_interval: int = 1,
|
|
353
|
+
**connection_kwargs: RedisValueT | None,
|
|
354
|
+
):
|
|
355
|
+
self.timeout = timeout
|
|
356
|
+
self.queue_class = queue_class
|
|
357
|
+
self.total_wait = 0
|
|
358
|
+
self.total_allocated = 0
|
|
359
|
+
max_connections = max_connections or 50
|
|
360
|
+
|
|
361
|
+
super().__init__(
|
|
362
|
+
connection_class=connection_class or Connection,
|
|
363
|
+
max_connections=max_connections,
|
|
364
|
+
max_idle_time=max_idle_time,
|
|
365
|
+
idle_check_interval=idle_check_interval,
|
|
366
|
+
**connection_kwargs,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
async def disconnect_on_idle_time_exceeded(self, connection: Connection) -> None:
|
|
370
|
+
while True:
|
|
371
|
+
if time.time() - connection.last_active_at > self.max_idle_time:
|
|
372
|
+
# Unlike the non blocking pool, we don't free the connection object,
|
|
373
|
+
# but always reuse it
|
|
374
|
+
connection.disconnect()
|
|
375
|
+
|
|
376
|
+
break
|
|
377
|
+
await asyncio.sleep(self.idle_check_interval)
|
|
378
|
+
|
|
379
|
+
def reset(self) -> None:
|
|
380
|
+
self._pool: asyncio.Queue[Connection | None] = self.queue_class(self.max_connections)
|
|
381
|
+
|
|
382
|
+
while True:
|
|
383
|
+
try:
|
|
384
|
+
self._pool.put_nowait(None)
|
|
385
|
+
except asyncio.QueueFull:
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
super().reset()
|
|
389
|
+
|
|
390
|
+
def peek_available(self) -> BaseConnection | None:
|
|
391
|
+
return (
|
|
392
|
+
self._pool._queue[-1] # type: ignore
|
|
393
|
+
if (self._pool and not self._pool.empty())
|
|
394
|
+
else None
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
async def get_connection(
|
|
398
|
+
self,
|
|
399
|
+
command_name: bytes | None = None,
|
|
400
|
+
*args: RedisValueT,
|
|
401
|
+
acquire: bool = True,
|
|
402
|
+
**kwargs: RedisValueT | None,
|
|
403
|
+
) -> Connection:
|
|
404
|
+
"""Gets a connection from the pool"""
|
|
405
|
+
self.checkpid()
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
async with async_timeout.timeout(self.timeout):
|
|
409
|
+
connection = await self._pool.get()
|
|
410
|
+
if connection and connection.is_connected and connection.needs_handshake:
|
|
411
|
+
await connection.perform_handshake()
|
|
412
|
+
except asyncio.TimeoutError:
|
|
413
|
+
raise ConnectionError("No connection available.")
|
|
414
|
+
if connection is None:
|
|
415
|
+
connection = self._make_connection()
|
|
416
|
+
|
|
417
|
+
if acquire:
|
|
418
|
+
self._in_use_connections.add(connection)
|
|
419
|
+
else:
|
|
420
|
+
self._pool.put_nowait(connection)
|
|
421
|
+
|
|
422
|
+
return connection
|
|
423
|
+
|
|
424
|
+
def release(self, connection: Connection) -> None:
|
|
425
|
+
"""Releases the connection back to the pool"""
|
|
426
|
+
_connection: Connection | None = connection
|
|
427
|
+
|
|
428
|
+
self.checkpid()
|
|
429
|
+
|
|
430
|
+
if _connection and _connection.pid == self.pid:
|
|
431
|
+
self._in_use_connections.remove(_connection)
|
|
432
|
+
try:
|
|
433
|
+
self._pool.put_nowait(_connection)
|
|
434
|
+
except asyncio.QueueFull:
|
|
435
|
+
_connection.disconnect()
|
|
436
|
+
|
|
437
|
+
def disconnect(self) -> None:
|
|
438
|
+
"""Closes all connections in the pool"""
|
|
439
|
+
pooled_connections: list[Connection | None] = []
|
|
440
|
+
|
|
441
|
+
while True:
|
|
442
|
+
try:
|
|
443
|
+
pooled_connections.append(self._pool.get_nowait())
|
|
444
|
+
except asyncio.QueueEmpty:
|
|
445
|
+
break
|
|
446
|
+
for conn in pooled_connections:
|
|
447
|
+
self._pool.put_nowait(conn)
|
|
448
|
+
|
|
449
|
+
all_conns = chain(pooled_connections, self._in_use_connections)
|
|
450
|
+
|
|
451
|
+
for connection in all_conns:
|
|
452
|
+
if connection is not None:
|
|
453
|
+
connection.disconnect()
|