Skip to content

Commit

Permalink
Merge branch 'master' into vv-deprecate-pypy3.8
Browse files Browse the repository at this point in the history
  • Loading branch information
vladvildanov authored Sep 4, 2024
2 parents 524796c + b4fcbaa commit 2d8e24c
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 147 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
* Prevent async ClusterPipeline instances from becoming "false-y" in case of empty command stack (#3061)
* Close Unix sockets if the connection attempt fails. This prevents `ResourceWarning`s. (#3314)
* Close SSL sockets if the connection attempt fails, or if validations fail. (#3317)
* Eliminate mutable default arguments in the `redis.commands.core.Script` class. (#3332)

* 4.1.3 (Feb 8, 2022)
* Fix flushdb and flushall (#1926)
Expand Down
2 changes: 1 addition & 1 deletion dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ invoke==2.2.0
mock
packaging>=20.4
pytest
pytest-asyncio
pytest-asyncio>=0.23.0,<0.24.0
pytest-cov
pytest-profiling
pytest-timeout
Expand Down
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ timeout = 30
filterwarnings =
always
ignore:RedisGraph support is deprecated as of Redis Stack 7.2:DeprecationWarning
# Ignore a coverage warning when COVERAGE_CORE=sysmon for Pythons < 3.12.
ignore:sys.monitoring isn't available:coverage.exceptions.CoverageWarning
6 changes: 4 additions & 2 deletions redis/_parsers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,11 @@ def parse_cluster_info(response, **options):
def _parse_node_line(line):
line_items = line.split(" ")
node_id, addr, flags, master_id, ping, pong, epoch, connected = line.split(" ")[:8]
addr = addr.split("@")[0]
ip = addr.split("@")[0]
hostname = addr.split("@")[1].split(",")[1] if "@" in addr and "," in addr else ""
node_dict = {
"node_id": node_id,
"hostname": hostname,
"flags": flags,
"master_id": master_id,
"last_ping_sent": ping,
Expand All @@ -460,7 +462,7 @@ def _parse_node_line(line):
if len(line_items) >= 9:
slots, migrations = _parse_slots(line_items[8:])
node_dict["slots"], node_dict["migrations"] = slots, migrations
return addr, node_dict
return ip, node_dict


def _parse_slots(slot_ranges):
Expand Down
10 changes: 6 additions & 4 deletions redis/asyncio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,10 +579,12 @@ async def aclose(self, close_connection_pool: Optional[bool] = None) -> None:
"""
Closes Redis client connection
: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.
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.
"""
conn = self.connection
if conn:
Expand Down
139 changes: 55 additions & 84 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
from .helpers import list_or_args

if TYPE_CHECKING:
from redis.asyncio.client import Redis as AsyncRedis
from redis.client import Redis
import redis.asyncio.client
import redis.client


class ACLCommands(CommandsProtocol):
Expand Down Expand Up @@ -731,16 +731,19 @@ def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
For more information see https://redis.io/commands/client-pause
:param timeout: milliseconds to pause clients
:param all: If true (default) all client commands are blocked.
otherwise, clients are only blocked if they attempt to execute
a write command.
Args:
timeout: milliseconds to pause clients
all: If true (default) all client commands are blocked.
otherwise, clients are only blocked if they attempt to execute
a write command.
For the WRITE mode, some commands have special behavior:
EVAL/EVALSHA: Will block client for all scripts.
PUBLISH: Will block client.
PFCOUNT: Will block client.
WAIT: Acknowledgments will be delayed, so this command will
appear blocked.
* EVAL/EVALSHA: Will block client for all scripts.
* PUBLISH: Will block client.
* PFCOUNT: Will block client.
* WAIT: Acknowledgments will be delayed, so this command will
appear blocked.
"""
args = ["CLIENT PAUSE", str(timeout)]
if not isinstance(timeout, int):
Expand Down Expand Up @@ -1439,7 +1442,7 @@ class BitFieldOperation:

def __init__(
self,
client: Union["Redis", "AsyncRedis"],
client: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
key: str,
default_overflow: Union[str, None] = None,
):
Expand Down Expand Up @@ -1583,7 +1586,7 @@ def bitcount(
return self.execute_command("BITCOUNT", *params, keys=[key])

def bitfield(
self: Union["Redis", "AsyncRedis"],
self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
key: KeyT,
default_overflow: Union[str, None] = None,
) -> BitFieldOperation:
Expand All @@ -1596,7 +1599,7 @@ def bitfield(
return BitFieldOperation(self, key, default_overflow=default_overflow)

def bitfield_ro(
self: Union["Redis", "AsyncRedis"],
self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
key: KeyT,
encoding: str,
offset: BitfieldOffsetT,
Expand Down Expand Up @@ -5464,27 +5467,23 @@ class Script:
An executable Lua script object returned by ``register_script``
"""

def __init__(self, registered_client: "Redis", script: ScriptTextT):
def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT):
self.registered_client = registered_client
self.script = script
# Precalculate and store the SHA1 hex digest of the script.

if isinstance(script, str):
# We need the encoding from the client in order to generate an
# accurate byte representation of the script
try:
encoder = registered_client.connection_pool.get_encoder()
except AttributeError:
# Cluster
encoder = registered_client.get_encoder()
encoder = self.get_encoder()
script = encoder.encode(script)
self.sha = hashlib.sha1(script).hexdigest()

def __call__(
self,
keys: Union[Sequence[KeyT], None] = None,
args: Union[Iterable[EncodableT], None] = None,
client: Union["Redis", None] = None,
client: Union["redis.client.Redis", None] = None,
):
"""Execute the script, passing any required ``args``"""
keys = keys or []
Expand All @@ -5507,13 +5506,35 @@ def __call__(
self.sha = client.script_load(self.script)
return client.evalsha(self.sha, len(keys), *args)

def get_encoder(self):
"""Get the encoder to encode string scripts into bytes."""
try:
return self.registered_client.get_encoder()
except AttributeError:
# DEPRECATED
# In version <=4.1.2, this was the code we used to get the encoder.
# However, after 4.1.2 we added support for scripting in clustered
# redis. ClusteredRedis doesn't have a `.connection_pool` attribute
# so we changed the Script class to use
# `self.registered_client.get_encoder` (see above).
# However, that is technically a breaking change, as consumers who
# use Scripts directly might inject a `registered_client` that
# doesn't have a `.get_encoder` field. This try/except prevents us
# from breaking backward-compatibility. Ideally, it would be
# removed in the next major release.
return self.registered_client.connection_pool.get_encoder()


class AsyncScript:
"""
An executable Lua script object returned by ``register_script``
"""

def __init__(self, registered_client: "AsyncRedis", script: ScriptTextT):
def __init__(
self,
registered_client: "redis.asyncio.client.Redis",
script: ScriptTextT,
):
self.registered_client = registered_client
self.script = script
# Precalculate and store the SHA1 hex digest of the script.
Expand All @@ -5533,7 +5554,7 @@ async def __call__(
self,
keys: Union[Sequence[KeyT], None] = None,
args: Union[Iterable[EncodableT], None] = None,
client: Union["AsyncRedis", None] = None,
client: Union["redis.asyncio.client.Redis", None] = None,
):
"""Execute the script, passing any required ``args``"""
keys = keys or []
Expand Down Expand Up @@ -5758,7 +5779,7 @@ def script_load(self, script: ScriptTextT) -> ResponseT:
"""
return self.execute_command("SCRIPT LOAD", script)

def register_script(self: "Redis", script: ScriptTextT) -> Script:
def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script:
"""
Register a Lua ``script`` specifying the ``keys`` it will touch.
Returns a Script object that is callable and hides the complexity of
Expand All @@ -5772,7 +5793,10 @@ class AsyncScriptCommands(ScriptCommands):
async def script_debug(self, *args) -> None:
return super().script_debug()

def register_script(self: "AsyncRedis", script: ScriptTextT) -> AsyncScript:
def register_script(
self: "redis.asyncio.client.Redis",
script: ScriptTextT,
) -> AsyncScript:
"""
Register a Lua ``script`` specifying the ``keys`` it will touch.
Returns a Script object that is callable and hides the complexity of
Expand Down Expand Up @@ -6283,62 +6307,6 @@ def command(self) -> ResponseT:
return self.execute_command("COMMAND")


class Script:
"""
An executable Lua script object returned by ``register_script``
"""

def __init__(self, registered_client, script):
self.registered_client = registered_client
self.script = script
# Precalculate and store the SHA1 hex digest of the script.

if isinstance(script, str):
# We need the encoding from the client in order to generate an
# accurate byte representation of the script
encoder = self.get_encoder()
script = encoder.encode(script)
self.sha = hashlib.sha1(script).hexdigest()

def __call__(self, keys=[], args=[], client=None):
"Execute the script, passing any required ``args``"
if client is None:
client = self.registered_client
args = tuple(keys) + tuple(args)
# make sure the Redis server knows about the script
from redis.client import Pipeline

if isinstance(client, Pipeline):
# Make sure the pipeline can register the script before executing.
client.scripts.add(self)
try:
return client.evalsha(self.sha, len(keys), *args)
except NoScriptError:
# Maybe the client is pointed to a different server than the client
# that created this instance?
# Overwrite the sha just in case there was a discrepancy.
self.sha = client.script_load(self.script)
return client.evalsha(self.sha, len(keys), *args)

def get_encoder(self):
"""Get the encoder to encode string scripts into bytes."""
try:
return self.registered_client.get_encoder()
except AttributeError:
# DEPRECATED
# In version <=4.1.2, this was the code we used to get the encoder.
# However, after 4.1.2 we added support for scripting in clustered
# redis. ClusteredRedis doesn't have a `.connection_pool` attribute
# so we changed the Script class to use
# `self.registered_client.get_encoder` (see above).
# However, that is technically a breaking change, as consumers who
# use Scripts directly might inject a `registered_client` that
# doesn't have a `.get_encoder` field. This try/except prevents us
# from breaking backward-compatibility. Ideally, it would be
# removed in the next major release.
return self.registered_client.connection_pool.get_encoder()


class AsyncModuleCommands(ModuleCommands):
async def command_info(self) -> None:
return super().command_info()
Expand Down Expand Up @@ -6415,9 +6383,12 @@ def function_list(
) -> Union[Awaitable[List], List]:
"""
Return information about the functions and libraries.
:param library: pecify a pattern for matching library names
:param withcode: cause the server to include the libraries source
implementation in the reply
Args:
library: specify a pattern for matching library names
withcode: cause the server to include the libraries source implementation
in the reply
"""
args = ["LIBRARYNAME", library]
if withcode:
Expand Down
Loading

0 comments on commit 2d8e24c

Please sign in to comment.