summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--redis/client.py11
-rw-r--r--tests/test_scripting.py81
2 files changed, 91 insertions, 1 deletions
diff --git a/redis/client.py b/redis/client.py
index 78b3dbe..27162f6 100644
--- a/redis/client.py
+++ b/redis/client.py
@@ -2383,11 +2383,14 @@ class BasePipeline(object):
scripts = list(self.scripts)
immediate = self.immediate_execute_command
shas = [s.sha for s in scripts]
+ # we can't use the normal script_* methods because they would just
+ # get buffered in the pipeline.
exists = immediate('SCRIPT', 'EXISTS', *shas, **{'parse': 'EXISTS'})
if not all(exists):
for s, exist in izip(scripts, exists):
if not exist:
- immediate('SCRIPT', 'LOAD', s.script, **{'parse': 'LOAD'})
+ s.sha = immediate('SCRIPT', 'LOAD', s.script,
+ **{'parse': 'LOAD'})
def execute(self, raise_on_error=True):
"Execute all the commands in the current pipeline"
@@ -2439,6 +2442,12 @@ class BasePipeline(object):
def script_load_for_pipeline(self, script):
"Make sure scripts are loaded prior to pipeline execution"
+ # we need the sha now so that Script.__call__ can use it to run
+ # evalsha.
+ if not script.sha:
+ script.sha = self.immediate_execute_command('SCRIPT', 'LOAD',
+ script.script,
+ **{'parse': 'LOAD'})
self.scripts.add(script)
diff --git a/tests/test_scripting.py b/tests/test_scripting.py
new file mode 100644
index 0000000..48f6138
--- /dev/null
+++ b/tests/test_scripting.py
@@ -0,0 +1,81 @@
+from __future__ import with_statement
+import pytest
+
+from redis import exceptions
+
+
+multiply_script = """
+local value = redis.call('GET', KEYS[1])
+value = tonumber(value)
+return value * ARGV[1]"""
+
+
+class TestScripting(object):
+ @pytest.fixture(autouse=True)
+ def reset_scripts(self, r):
+ r.script_flush()
+
+ def test_eval(self, r):
+ r.set('a', 2)
+ # 2 * 3 == 6
+ assert r.eval(multiply_script, 1, 'a', 3) == 6
+
+ def test_evalsha(self, r):
+ r.set('a', 2)
+ sha = r.script_load(multiply_script)
+ # 2 * 3 == 6
+ assert r.evalsha(sha, 1, 'a', 3) == 6
+
+ def test_evalsha_script_not_loaded(self, r):
+ r.set('a', 2)
+ sha = r.script_load(multiply_script)
+ # remove the script from Redis's cache
+ r.script_flush()
+ with pytest.raises(exceptions.NoScriptError):
+ r.evalsha(sha, 1, 'a', 3)
+
+ def test_script_loading(self, r):
+ # get the sha, then clear the cache
+ sha = r.script_load(multiply_script)
+ r.script_flush()
+ assert r.script_exists(sha) == [False]
+ r.script_load(multiply_script)
+ assert r.script_exists(sha) == [True]
+
+ def test_script_object(self, r):
+ r.set('a', 2)
+ multiply = r.register_script(multiply_script)
+ assert not multiply.sha
+ # test evalsha fail -> script load + retry
+ assert multiply(keys=['a'], args=[3]) == 6
+ assert multiply.sha
+ assert r.script_exists(multiply.sha) == [True]
+ # test first evalsha
+ assert multiply(keys=['a'], args=[3]) == 6
+
+ def test_script_object_in_pipeline(self, r):
+ multiply = r.register_script(multiply_script)
+ assert not multiply.sha
+ pipe = r.pipeline()
+ pipe.set('a', 2)
+ pipe.get('a')
+ multiply(keys=['a'], args=[3], client=pipe)
+ # even though the pipeline wasn't executed yet, we made sure the
+ # script was loaded and got a valid sha
+ assert multiply.sha
+ assert r.script_exists(multiply.sha) == [True]
+ # [SET worked, GET 'a', result of multiple script]
+ assert pipe.execute() == [True, '2', 6]
+
+ # purge the script from redis's cache and re-run the pipeline
+ # the multiply script object knows it's sha, so it shouldn't get
+ # reloaded until pipe.execute()
+ r.script_flush()
+ pipe = r.pipeline()
+ pipe.set('a', 2)
+ pipe.get('a')
+ assert multiply.sha
+ multiply(keys=['a'], args=[3], client=pipe)
+ assert r.script_exists(multiply.sha) == [False]
+ # [SET worked, GET 'a', result of multiple script]
+ assert pipe.execute() == [True, '2', 6]