diff --git a/CHANGES b/CHANGES index f0d75a45ce..8750128b05 100644 --- a/CHANGES +++ b/CHANGES @@ -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) diff --git a/redis/commands/core.py b/redis/commands/core.py index 91f5a8661b..d46e55446c 100644 --- a/redis/commands/core.py +++ b/redis/commands/core.py @@ -5475,11 +5475,7 @@ def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT) 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() @@ -5510,6 +5506,24 @@ 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: """ @@ -6293,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()