This commit is contained in:
@@ -15,11 +15,9 @@ from typing import (
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Protocol,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
@@ -39,7 +37,6 @@ from redis.asyncio.connection import (
|
||||
)
|
||||
from redis.asyncio.lock import Lock
|
||||
from redis.asyncio.retry import Retry
|
||||
from redis.backoff import ExponentialWithJitterBackoff
|
||||
from redis.client import (
|
||||
EMPTY_RESPONSE,
|
||||
NEVER_DECODE,
|
||||
@@ -52,40 +49,27 @@ from redis.commands import (
|
||||
AsyncSentinelCommands,
|
||||
list_or_args,
|
||||
)
|
||||
from redis.compat import Protocol, TypedDict
|
||||
from redis.credentials import CredentialProvider
|
||||
from redis.event import (
|
||||
AfterPooledConnectionsInstantiationEvent,
|
||||
AfterPubSubConnectionInstantiationEvent,
|
||||
AfterSingleConnectionInstantiationEvent,
|
||||
ClientType,
|
||||
EventDispatcher,
|
||||
)
|
||||
from redis.exceptions import (
|
||||
ConnectionError,
|
||||
ExecAbortError,
|
||||
PubSubError,
|
||||
RedisError,
|
||||
ResponseError,
|
||||
TimeoutError,
|
||||
WatchError,
|
||||
)
|
||||
from redis.typing import ChannelT, EncodableT, KeyT
|
||||
from redis.utils import (
|
||||
SSL_AVAILABLE,
|
||||
HIREDIS_AVAILABLE,
|
||||
_set_info_logger,
|
||||
deprecated_args,
|
||||
deprecated_function,
|
||||
get_lib_version,
|
||||
safe_str,
|
||||
str_if_bytes,
|
||||
truncate_text,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING and SSL_AVAILABLE:
|
||||
from ssl import TLSVersion, VerifyMode
|
||||
else:
|
||||
TLSVersion = None
|
||||
VerifyMode = None
|
||||
|
||||
PubSubHandler = Callable[[Dict[str, str]], Awaitable[None]]
|
||||
_KeyT = TypeVar("_KeyT", bound=KeyT)
|
||||
_ArgT = TypeVar("_ArgT", KeyT, EncodableT)
|
||||
@@ -96,11 +80,13 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class ResponseCallbackProtocol(Protocol):
|
||||
def __call__(self, response: Any, **kwargs): ...
|
||||
def __call__(self, response: Any, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
class AsyncResponseCallbackProtocol(Protocol):
|
||||
async def __call__(self, response: Any, **kwargs): ...
|
||||
async def __call__(self, response: Any, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
ResponseCallbackT = Union[ResponseCallbackProtocol, AsyncResponseCallbackProtocol]
|
||||
@@ -182,7 +168,7 @@ class Redis(
|
||||
warnings.warn(
|
||||
DeprecationWarning(
|
||||
'"auto_close_connection_pool" is deprecated '
|
||||
"since version 5.0.1. "
|
||||
"since version 5.0.0. "
|
||||
"Please create a ConnectionPool explicitly and "
|
||||
"provide to the Redis() constructor instead."
|
||||
)
|
||||
@@ -208,11 +194,6 @@ class Redis(
|
||||
client.auto_close_connection_pool = True
|
||||
return client
|
||||
|
||||
@deprecated_args(
|
||||
args_to_warn=["retry_on_timeout"],
|
||||
reason="TimeoutError is included by default.",
|
||||
version="6.0.0",
|
||||
)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -230,19 +211,14 @@ class Redis(
|
||||
encoding_errors: str = "strict",
|
||||
decode_responses: bool = False,
|
||||
retry_on_timeout: bool = False,
|
||||
retry: Retry = Retry(
|
||||
backoff=ExponentialWithJitterBackoff(base=1, cap=10), retries=3
|
||||
),
|
||||
retry_on_error: Optional[list] = None,
|
||||
ssl: bool = False,
|
||||
ssl_keyfile: Optional[str] = None,
|
||||
ssl_certfile: Optional[str] = None,
|
||||
ssl_cert_reqs: Union[str, VerifyMode] = "required",
|
||||
ssl_cert_reqs: str = "required",
|
||||
ssl_ca_certs: Optional[str] = None,
|
||||
ssl_ca_data: Optional[str] = None,
|
||||
ssl_check_hostname: bool = True,
|
||||
ssl_min_version: Optional[TLSVersion] = None,
|
||||
ssl_ciphers: Optional[str] = None,
|
||||
ssl_check_hostname: bool = False,
|
||||
max_connections: Optional[int] = None,
|
||||
single_connection_client: bool = False,
|
||||
health_check_interval: int = 0,
|
||||
@@ -250,38 +226,20 @@ class Redis(
|
||||
lib_name: Optional[str] = "redis-py",
|
||||
lib_version: Optional[str] = get_lib_version(),
|
||||
username: Optional[str] = None,
|
||||
retry: Optional[Retry] = None,
|
||||
auto_close_connection_pool: Optional[bool] = None,
|
||||
redis_connect_func=None,
|
||||
credential_provider: Optional[CredentialProvider] = None,
|
||||
protocol: Optional[int] = 2,
|
||||
event_dispatcher: Optional[EventDispatcher] = None,
|
||||
):
|
||||
"""
|
||||
Initialize a new Redis client.
|
||||
|
||||
To specify a retry policy for specific errors, you have two options:
|
||||
|
||||
1. Set the `retry_on_error` to a list of the error/s to retry on, and
|
||||
you can also set `retry` to a valid `Retry` object(in case the default
|
||||
one is not appropriate) - with this approach the retries will be triggered
|
||||
on the default errors specified in the Retry object enriched with the
|
||||
errors specified in `retry_on_error`.
|
||||
|
||||
2. Define a `Retry` object with configured 'supported_errors' and set
|
||||
it to the `retry` parameter - with this approach you completely redefine
|
||||
the errors on which retries will happen.
|
||||
|
||||
`retry_on_timeout` is deprecated - please include the TimeoutError
|
||||
either in the Retry object or in the `retry_on_error` list.
|
||||
|
||||
When 'connection_pool' is provided - the retry configuration of the
|
||||
provided pool will be used.
|
||||
To specify a retry policy for specific errors, first set
|
||||
`retry_on_error` to a list of the error/s to retry on, then set
|
||||
`retry` to a valid `Retry` object.
|
||||
To retry on TimeoutError, `retry_on_timeout` can also be set to `True`.
|
||||
"""
|
||||
kwargs: Dict[str, Any]
|
||||
if event_dispatcher is None:
|
||||
self._event_dispatcher = EventDispatcher()
|
||||
else:
|
||||
self._event_dispatcher = event_dispatcher
|
||||
# auto_close_connection_pool only has an effect if connection_pool is
|
||||
# None. It is assumed that if connection_pool is not None, the user
|
||||
# wants to manage the connection pool themselves.
|
||||
@@ -289,7 +247,7 @@ class Redis(
|
||||
warnings.warn(
|
||||
DeprecationWarning(
|
||||
'"auto_close_connection_pool" is deprecated '
|
||||
"since version 5.0.1. "
|
||||
"since version 5.0.0. "
|
||||
"Please create a ConnectionPool explicitly and "
|
||||
"provide to the Redis() constructor instead."
|
||||
)
|
||||
@@ -301,6 +259,8 @@ class Redis(
|
||||
# Create internal connection pool, expected to be closed by Redis instance
|
||||
if not retry_on_error:
|
||||
retry_on_error = []
|
||||
if retry_on_timeout is True:
|
||||
retry_on_error.append(TimeoutError)
|
||||
kwargs = {
|
||||
"db": db,
|
||||
"username": username,
|
||||
@@ -310,6 +270,7 @@ class Redis(
|
||||
"encoding": encoding,
|
||||
"encoding_errors": encoding_errors,
|
||||
"decode_responses": decode_responses,
|
||||
"retry_on_timeout": retry_on_timeout,
|
||||
"retry_on_error": retry_on_error,
|
||||
"retry": copy.deepcopy(retry),
|
||||
"max_connections": max_connections,
|
||||
@@ -350,26 +311,14 @@ class Redis(
|
||||
"ssl_ca_certs": ssl_ca_certs,
|
||||
"ssl_ca_data": ssl_ca_data,
|
||||
"ssl_check_hostname": ssl_check_hostname,
|
||||
"ssl_min_version": ssl_min_version,
|
||||
"ssl_ciphers": ssl_ciphers,
|
||||
}
|
||||
)
|
||||
# This arg only used if no pool is passed in
|
||||
self.auto_close_connection_pool = auto_close_connection_pool
|
||||
connection_pool = ConnectionPool(**kwargs)
|
||||
self._event_dispatcher.dispatch(
|
||||
AfterPooledConnectionsInstantiationEvent(
|
||||
[connection_pool], ClientType.ASYNC, credential_provider
|
||||
)
|
||||
)
|
||||
else:
|
||||
# If a pool is passed in, do not close it
|
||||
self.auto_close_connection_pool = False
|
||||
self._event_dispatcher.dispatch(
|
||||
AfterPooledConnectionsInstantiationEvent(
|
||||
[connection_pool], ClientType.ASYNC, credential_provider
|
||||
)
|
||||
)
|
||||
|
||||
self.connection_pool = connection_pool
|
||||
self.single_connection_client = single_connection_client
|
||||
@@ -388,10 +337,7 @@ class Redis(
|
||||
self._single_conn_lock = asyncio.Lock()
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"<{self.__class__.__module__}.{self.__class__.__name__}"
|
||||
f"({self.connection_pool!r})>"
|
||||
)
|
||||
return f"{self.__class__.__name__}<{self.connection_pool!r}>"
|
||||
|
||||
def __await__(self):
|
||||
return self.initialize().__await__()
|
||||
@@ -400,13 +346,7 @@ class Redis(
|
||||
if self.single_connection_client:
|
||||
async with self._single_conn_lock:
|
||||
if self.connection is None:
|
||||
self.connection = await self.connection_pool.get_connection()
|
||||
|
||||
self._event_dispatcher.dispatch(
|
||||
AfterSingleConnectionInstantiationEvent(
|
||||
self.connection, ClientType.ASYNC, self._single_conn_lock
|
||||
)
|
||||
)
|
||||
self.connection = await self.connection_pool.get_connection("_")
|
||||
return self
|
||||
|
||||
def set_response_callback(self, command: str, callback: ResponseCallbackT):
|
||||
@@ -421,10 +361,10 @@ class Redis(
|
||||
"""Get the connection's key-word arguments"""
|
||||
return self.connection_pool.connection_kwargs
|
||||
|
||||
def get_retry(self) -> Optional[Retry]:
|
||||
def get_retry(self) -> Optional["Retry"]:
|
||||
return self.get_connection_kwargs().get("retry")
|
||||
|
||||
def set_retry(self, retry: Retry) -> None:
|
||||
def set_retry(self, retry: "Retry") -> None:
|
||||
self.get_connection_kwargs().update({"retry": retry})
|
||||
self.connection_pool.set_retry(retry)
|
||||
|
||||
@@ -503,7 +443,6 @@ class Redis(
|
||||
blocking_timeout: Optional[float] = None,
|
||||
lock_class: Optional[Type[Lock]] = None,
|
||||
thread_local: bool = True,
|
||||
raise_on_release_error: bool = True,
|
||||
) -> Lock:
|
||||
"""
|
||||
Return a new Lock object using key ``name`` that mimics
|
||||
@@ -550,11 +489,6 @@ class Redis(
|
||||
thread-1 would see the token value as "xyz" and would be
|
||||
able to successfully release the thread-2's lock.
|
||||
|
||||
``raise_on_release_error`` indicates whether to raise an exception when
|
||||
the lock is no longer owned when exiting the context manager. By default,
|
||||
this is True, meaning an exception will be raised. If False, the warning
|
||||
will be logged and the exception will be suppressed.
|
||||
|
||||
In some use cases it's necessary to disable thread local storage. For
|
||||
example, if you have code where one thread acquires a lock and passes
|
||||
that lock instance to a worker thread to release later. If thread
|
||||
@@ -572,7 +506,6 @@ class Redis(
|
||||
blocking=blocking,
|
||||
blocking_timeout=blocking_timeout,
|
||||
thread_local=thread_local,
|
||||
raise_on_release_error=raise_on_release_error,
|
||||
)
|
||||
|
||||
def pubsub(self, **kwargs) -> "PubSub":
|
||||
@@ -581,9 +514,7 @@ class Redis(
|
||||
subscribe to channels and listen for messages that get published to
|
||||
them.
|
||||
"""
|
||||
return PubSub(
|
||||
self.connection_pool, event_dispatcher=self._event_dispatcher, **kwargs
|
||||
)
|
||||
return PubSub(self.connection_pool, **kwargs)
|
||||
|
||||
def monitor(self) -> "Monitor":
|
||||
return Monitor(self.connection_pool)
|
||||
@@ -615,18 +546,15 @@ class Redis(
|
||||
_grl().call_exception_handler(context)
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.connection._close()
|
||||
|
||||
async def aclose(self, close_connection_pool: Optional[bool] = None) -> None:
|
||||
"""
|
||||
Closes Redis client connection
|
||||
|
||||
Args:
|
||||
close_connection_pool:
|
||||
decides whether to close the connection pool used by this Redis client,
|
||||
overriding Redis.auto_close_connection_pool.
|
||||
By default, let Redis.auto_close_connection_pool decide
|
||||
whether to close the connection pool.
|
||||
:param close_connection_pool: decides whether to close the connection pool used
|
||||
by this Redis client, overriding Redis.auto_close_connection_pool. By default,
|
||||
let Redis.auto_close_connection_pool decide whether to close the connection
|
||||
pool.
|
||||
"""
|
||||
conn = self.connection
|
||||
if conn:
|
||||
@@ -637,7 +565,7 @@ class Redis(
|
||||
):
|
||||
await self.connection_pool.disconnect()
|
||||
|
||||
@deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close")
|
||||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close")
|
||||
async def close(self, close_connection_pool: Optional[bool] = None) -> None:
|
||||
"""
|
||||
Alias for aclose(), for backwards compatibility
|
||||
@@ -651,17 +579,18 @@ class Redis(
|
||||
await conn.send_command(*args)
|
||||
return await self.parse_response(conn, command_name, **options)
|
||||
|
||||
async def _close_connection(self, conn: Connection):
|
||||
async def _disconnect_raise(self, conn: Connection, error: Exception):
|
||||
"""
|
||||
Close the connection before retrying.
|
||||
|
||||
The supported exceptions are already checked in the
|
||||
retry object so we don't need to do it here.
|
||||
|
||||
After we disconnect the connection, it will try to reconnect and
|
||||
do a health check as part of the send_command logic(on connection level).
|
||||
Close the connection and raise an exception
|
||||
if retry_on_error is not set or the error
|
||||
is not one of the specified error types
|
||||
"""
|
||||
await conn.disconnect()
|
||||
if (
|
||||
conn.retry_on_error is None
|
||||
or isinstance(error, tuple(conn.retry_on_error)) is False
|
||||
):
|
||||
raise error
|
||||
|
||||
# COMMAND EXECUTION AND PROTOCOL PARSING
|
||||
async def execute_command(self, *args, **options):
|
||||
@@ -669,7 +598,7 @@ class Redis(
|
||||
await self.initialize()
|
||||
pool = self.connection_pool
|
||||
command_name = args[0]
|
||||
conn = self.connection or await pool.get_connection()
|
||||
conn = self.connection or await pool.get_connection(command_name, **options)
|
||||
|
||||
if self.single_connection_client:
|
||||
await self._single_conn_lock.acquire()
|
||||
@@ -678,7 +607,7 @@ class Redis(
|
||||
lambda: self._send_command_parse_response(
|
||||
conn, command_name, *args, **options
|
||||
),
|
||||
lambda _: self._close_connection(conn),
|
||||
lambda error: self._disconnect_raise(conn, error),
|
||||
)
|
||||
finally:
|
||||
if self.single_connection_client:
|
||||
@@ -704,9 +633,6 @@ class Redis(
|
||||
if EMPTY_RESPONSE in options:
|
||||
options.pop(EMPTY_RESPONSE)
|
||||
|
||||
# Remove keys entry, it needs only for cache.
|
||||
options.pop("keys", None)
|
||||
|
||||
if command_name in self.response_callbacks:
|
||||
# Mypy bug: https://github.com/python/mypy/issues/10977
|
||||
command_name = cast(str, command_name)
|
||||
@@ -743,7 +669,7 @@ class Monitor:
|
||||
|
||||
async def connect(self):
|
||||
if self.connection is None:
|
||||
self.connection = await self.connection_pool.get_connection()
|
||||
self.connection = await self.connection_pool.get_connection("MONITOR")
|
||||
|
||||
async def __aenter__(self):
|
||||
await self.connect()
|
||||
@@ -820,12 +746,7 @@ class PubSub:
|
||||
ignore_subscribe_messages: bool = False,
|
||||
encoder=None,
|
||||
push_handler_func: Optional[Callable] = None,
|
||||
event_dispatcher: Optional["EventDispatcher"] = None,
|
||||
):
|
||||
if event_dispatcher is None:
|
||||
self._event_dispatcher = EventDispatcher()
|
||||
else:
|
||||
self._event_dispatcher = event_dispatcher
|
||||
self.connection_pool = connection_pool
|
||||
self.shard_hint = shard_hint
|
||||
self.ignore_subscribe_messages = ignore_subscribe_messages
|
||||
@@ -862,7 +783,7 @@ class PubSub:
|
||||
|
||||
def __del__(self):
|
||||
if self.connection:
|
||||
self.connection.deregister_connect_callback(self.on_connect)
|
||||
self.connection._deregister_connect_callback(self.on_connect)
|
||||
|
||||
async def aclose(self):
|
||||
# In case a connection property does not yet exist
|
||||
@@ -873,7 +794,7 @@ class PubSub:
|
||||
async with self._lock:
|
||||
if self.connection:
|
||||
await self.connection.disconnect()
|
||||
self.connection.deregister_connect_callback(self.on_connect)
|
||||
self.connection._deregister_connect_callback(self.on_connect)
|
||||
await self.connection_pool.release(self.connection)
|
||||
self.connection = None
|
||||
self.channels = {}
|
||||
@@ -881,12 +802,12 @@ class PubSub:
|
||||
self.patterns = {}
|
||||
self.pending_unsubscribe_patterns = set()
|
||||
|
||||
@deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close")
|
||||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close")
|
||||
async def close(self) -> None:
|
||||
"""Alias for aclose(), for backwards compatibility"""
|
||||
await self.aclose()
|
||||
|
||||
@deprecated_function(version="5.0.1", reason="Use aclose() instead", name="reset")
|
||||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="reset")
|
||||
async def reset(self) -> None:
|
||||
"""Alias for aclose(), for backwards compatibility"""
|
||||
await self.aclose()
|
||||
@@ -931,26 +852,26 @@ class PubSub:
|
||||
Ensure that the PubSub is connected
|
||||
"""
|
||||
if self.connection is None:
|
||||
self.connection = await self.connection_pool.get_connection()
|
||||
self.connection = await self.connection_pool.get_connection(
|
||||
"pubsub", self.shard_hint
|
||||
)
|
||||
# register a callback that re-subscribes to any channels we
|
||||
# were listening to when we were disconnected
|
||||
self.connection.register_connect_callback(self.on_connect)
|
||||
self.connection._register_connect_callback(self.on_connect)
|
||||
else:
|
||||
await self.connection.connect()
|
||||
if self.push_handler_func is not None:
|
||||
self.connection._parser.set_pubsub_push_handler(self.push_handler_func)
|
||||
if self.push_handler_func is not None and not HIREDIS_AVAILABLE:
|
||||
self.connection._parser.set_push_handler(self.push_handler_func)
|
||||
|
||||
self._event_dispatcher.dispatch(
|
||||
AfterPubSubConnectionInstantiationEvent(
|
||||
self.connection, self.connection_pool, ClientType.ASYNC, self._lock
|
||||
)
|
||||
)
|
||||
|
||||
async def _reconnect(self, conn):
|
||||
async def _disconnect_raise_connect(self, conn, error):
|
||||
"""
|
||||
Try to reconnect
|
||||
Close the connection and raise an exception
|
||||
if retry_on_timeout is not set or the error
|
||||
is not a TimeoutError. Otherwise, try to reconnect
|
||||
"""
|
||||
await conn.disconnect()
|
||||
if not (conn.retry_on_timeout and isinstance(error, TimeoutError)):
|
||||
raise error
|
||||
await conn.connect()
|
||||
|
||||
async def _execute(self, conn, command, *args, **kwargs):
|
||||
@@ -963,7 +884,7 @@ class PubSub:
|
||||
"""
|
||||
return await conn.retry.call_with_retry(
|
||||
lambda: command(*args, **kwargs),
|
||||
lambda _: self._reconnect(conn),
|
||||
lambda error: self._disconnect_raise_connect(conn, error),
|
||||
)
|
||||
|
||||
async def parse_response(self, block: bool = True, timeout: float = 0):
|
||||
@@ -1232,11 +1153,13 @@ class PubSub:
|
||||
|
||||
|
||||
class PubsubWorkerExceptionHandler(Protocol):
|
||||
def __call__(self, e: BaseException, pubsub: PubSub): ...
|
||||
def __call__(self, e: BaseException, pubsub: PubSub):
|
||||
...
|
||||
|
||||
|
||||
class AsyncPubsubWorkerExceptionHandler(Protocol):
|
||||
async def __call__(self, e: BaseException, pubsub: PubSub): ...
|
||||
async def __call__(self, e: BaseException, pubsub: PubSub):
|
||||
...
|
||||
|
||||
|
||||
PSWorkerThreadExcHandlerT = Union[
|
||||
@@ -1254,8 +1177,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
in one transmission. This is convenient for batch processing, such as
|
||||
saving all the values in a list to Redis.
|
||||
|
||||
All commands executed within a pipeline(when running in transactional mode,
|
||||
which is the default behavior) are wrapped with MULTI and EXEC
|
||||
All commands executed within a pipeline are wrapped with MULTI and EXEC
|
||||
calls. This guarantees all commands executed in the pipeline will be
|
||||
executed atomically.
|
||||
|
||||
@@ -1284,7 +1206,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
self.shard_hint = shard_hint
|
||||
self.watching = False
|
||||
self.command_stack: CommandStackT = []
|
||||
self.scripts: Set[Script] = set()
|
||||
self.scripts: Set["Script"] = set()
|
||||
self.explicit_transaction = False
|
||||
|
||||
async def __aenter__(self: _RedisT) -> _RedisT:
|
||||
@@ -1356,50 +1278,49 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
return self.immediate_execute_command(*args, **kwargs)
|
||||
return self.pipeline_execute_command(*args, **kwargs)
|
||||
|
||||
async def _disconnect_reset_raise_on_watching(
|
||||
self,
|
||||
conn: Connection,
|
||||
error: Exception,
|
||||
):
|
||||
async def _disconnect_reset_raise(self, conn, error):
|
||||
"""
|
||||
Close the connection reset watching state and
|
||||
raise an exception if we were watching.
|
||||
|
||||
The supported exceptions are already checked in the
|
||||
retry object so we don't need to do it here.
|
||||
|
||||
After we disconnect the connection, it will try to reconnect and
|
||||
do a health check as part of the send_command logic(on connection level).
|
||||
Close the connection, reset watching state and
|
||||
raise an exception if we were watching,
|
||||
retry_on_timeout is not set,
|
||||
or the error is not a TimeoutError
|
||||
"""
|
||||
await conn.disconnect()
|
||||
# if we were already watching a variable, the watch is no longer
|
||||
# valid since this connection has died. raise a WatchError, which
|
||||
# indicates the user should retry this transaction.
|
||||
if self.watching:
|
||||
await self.reset()
|
||||
await self.aclose()
|
||||
raise WatchError(
|
||||
f"A {type(error).__name__} occurred while watching one or more keys"
|
||||
"A ConnectionError occurred on while watching one or more keys"
|
||||
)
|
||||
# if retry_on_timeout is not set, or the error is not
|
||||
# a TimeoutError, raise it
|
||||
if not (conn.retry_on_timeout and isinstance(error, TimeoutError)):
|
||||
await self.aclose()
|
||||
raise
|
||||
|
||||
async def immediate_execute_command(self, *args, **options):
|
||||
"""
|
||||
Execute a command immediately, but don't auto-retry on the supported
|
||||
errors for retry if we're already WATCHing a variable.
|
||||
Used when issuing WATCH or subsequent commands retrieving their values but before
|
||||
Execute a command immediately, but don't auto-retry on a
|
||||
ConnectionError if we're already WATCHing a variable. Used when
|
||||
issuing WATCH or subsequent commands retrieving their values but before
|
||||
MULTI is called.
|
||||
"""
|
||||
command_name = args[0]
|
||||
conn = self.connection
|
||||
# if this is the first call, we need a connection
|
||||
if not conn:
|
||||
conn = await self.connection_pool.get_connection()
|
||||
conn = await self.connection_pool.get_connection(
|
||||
command_name, self.shard_hint
|
||||
)
|
||||
self.connection = conn
|
||||
|
||||
return await conn.retry.call_with_retry(
|
||||
lambda: self._send_command_parse_response(
|
||||
conn, command_name, *args, **options
|
||||
),
|
||||
lambda error: self._disconnect_reset_raise_on_watching(conn, error),
|
||||
lambda error: self._disconnect_reset_raise(conn, error),
|
||||
)
|
||||
|
||||
def pipeline_execute_command(self, *args, **options):
|
||||
@@ -1484,10 +1405,6 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
if not isinstance(r, Exception):
|
||||
args, options = cmd
|
||||
command_name = args[0]
|
||||
|
||||
# Remove keys entry, it needs only for cache.
|
||||
options.pop("keys", None)
|
||||
|
||||
if command_name in self.response_callbacks:
|
||||
r = self.response_callbacks[command_name](r, **options)
|
||||
if inspect.isawaitable(r):
|
||||
@@ -1525,10 +1442,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
self, exception: Exception, number: int, command: Iterable[object]
|
||||
) -> None:
|
||||
cmd = " ".join(map(safe_str, command))
|
||||
msg = (
|
||||
f"Command # {number} ({truncate_text(cmd)}) "
|
||||
"of pipeline caused error: {exception.args}"
|
||||
)
|
||||
msg = f"Command # {number} ({cmd}) of pipeline caused error: {exception.args}"
|
||||
exception.args = (msg,) + exception.args[1:]
|
||||
|
||||
async def parse_response(
|
||||
@@ -1554,15 +1468,11 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
if not exist:
|
||||
s.sha = await immediate("SCRIPT LOAD", s.script)
|
||||
|
||||
async def _disconnect_raise_on_watching(self, conn: Connection, error: Exception):
|
||||
async def _disconnect_raise_reset(self, conn: Connection, error: Exception):
|
||||
"""
|
||||
Close the connection, raise an exception if we were watching.
|
||||
|
||||
The supported exceptions are already checked in the
|
||||
retry object so we don't need to do it here.
|
||||
|
||||
After we disconnect the connection, it will try to reconnect and
|
||||
do a health check as part of the send_command logic(on connection level).
|
||||
Close the connection, raise an exception if we were watching,
|
||||
and raise an exception if retry_on_timeout is not set,
|
||||
or the error is not a TimeoutError
|
||||
"""
|
||||
await conn.disconnect()
|
||||
# if we were watching a variable, the watch is no longer valid
|
||||
@@ -1570,10 +1480,15 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
# indicates the user should retry this transaction.
|
||||
if self.watching:
|
||||
raise WatchError(
|
||||
f"A {type(error).__name__} occurred while watching one or more keys"
|
||||
"A ConnectionError occurred on while watching one or more keys"
|
||||
)
|
||||
# if retry_on_timeout is not set, or the error is not
|
||||
# a TimeoutError, raise it
|
||||
if not (conn.retry_on_timeout and isinstance(error, TimeoutError)):
|
||||
await self.reset()
|
||||
raise
|
||||
|
||||
async def execute(self, raise_on_error: bool = True) -> List[Any]:
|
||||
async def execute(self, raise_on_error: bool = True):
|
||||
"""Execute all the commands in the current pipeline"""
|
||||
stack = self.command_stack
|
||||
if not stack and not self.watching:
|
||||
@@ -1587,7 +1502,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
|
||||
conn = self.connection
|
||||
if not conn:
|
||||
conn = await self.connection_pool.get_connection()
|
||||
conn = await self.connection_pool.get_connection("MULTI", self.shard_hint)
|
||||
# assign to self.connection so reset() releases the connection
|
||||
# back to the pool after we're done
|
||||
self.connection = conn
|
||||
@@ -1596,7 +1511,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
||||
try:
|
||||
return await conn.retry.call_with_retry(
|
||||
lambda: execute(conn, stack, raise_on_error),
|
||||
lambda error: self._disconnect_raise_on_watching(conn, error),
|
||||
lambda error: self._disconnect_raise_reset(conn, error),
|
||||
)
|
||||
finally:
|
||||
await self.reset()
|
||||
|
||||
Reference in New Issue
Block a user