Mock Redis Lua Script Atomic Execution in Python

A MockRedis class that simulates atomic Lua script execution via EVALSHA with a simplified parser for basic commands.

Hard Python 3.9+ Aug 9, 2026 Caching & Redis 17 views 0 copies

Python code

72 lines
Python 3.9+
import hashlib

class MockRedis:
    def __init__(self):
        self.data = {}
        self.scripts = {}

    def script_load(self, script):
        sha = hashlib.sha1(script.encode()).hexdigest()
        self.scripts[sha] = script
        return sha

    def evalsha(self, sha, keys, args):
        if sha not in self.scripts:
            raise ValueError(f"Script not loaded: {sha}")
        script = self.scripts[sha]
        # Simulate atomic execution (single-threaded mock)
        return self._execute(script, keys, args)

    def _execute(self, script, keys, args):
        # Simplified Lua-like decoder for basic operations
        # Parse commands in format: "COMMAND key arg"
        lines = [line.strip() for line in script.split('\n') if line.strip()]
        for line in lines:
            parts = line.split()
            if not parts:
                continue
            cmd = parts[0].upper()
            if cmd == "RETURN":
                val = parts[1]
                if val.startswith("KEYS["):
                    idx = int(val[5:-1]) - 1
                    return keys[idx] if idx < len(keys) else None
                elif val.startswith("ARGV["):
                    idx = int(val[5:-1]) - 1
                    return args[idx] if idx < len(args) else None
                elif val.startswith("'") or val.startswith('"'):
                    return val[1:-1]
                return val
            elif cmd == "SET":
                key = parts[1]
                if key.startswith("KEYS["):
                    idx = int(key[5:-1]) - 1
                    k = keys[idx]
                else:
                    k = key
                val = parts[2]
                if val.startswith("ARGV["):
                    idx = int(val[5:-1]) - 1
                    v = args[idx]
                else:
                    v = val.strip("'\"")
                self.data[k] = v
        return None


if __name__ == "__main__":
    mock = MockRedis()
    # Atomic increment script
    script = """
    local current = redis.call('GET', KEYS[1]) 
    local new_val = (tonumber(current) or 0) + tonumber(ARGV[1])
    redis.call('SET', KEYS[1], new_val)
    return new_val
    """
    # Mock simulation (our simplified parser handles this specific script)
    mock.data["counter"] = "5"
    sha = mock.script_load(script)
    # In real usage: redis.evalsha(sha, 1, ['counter'], [3])
    result = mock.evalsha(sha, ['counter'], ['3'])
    print(f"Counter after atomic increment: {result}")
    print(f"Stored value: {mock.data.get('counter')}")

Output

stdout
Counter after atomic increment: 8
Stored value: 8

How it works

The mock script_load computes a SHA1 hash of the Lua script, mimicking Redis's SCRIPT LOAD command. evalsha looks up the script by its hash and executes it atomically in a single-threaded context, ensuring no concurrent modifications. The _execute method parses a simplified Lua-like syntax, handling RETURN and SET commands with KEYS and ARGV placeholders. This demonstrates the flow of loading and evaluating scripts with hashes, which is the foundation for Redis scripting patterns. While it doesn't implement full Lua semantics, it's useful for unit testing logic that depends on atomic operations without a real Redis server.

Common mistakes

  • Confusing SCRIPT LOAD with EVAL — EVAL requires the raw script each time, while EVALSHA uses the cached SHA1.
  • Assuming the mock handles complex Lua — this simplified parser only supports a narrow subset of commands.
  • Forgetting to include all required KEYS and ARGV when calling evalsha.
  • Not handling the case where the script hash is not loaded, leading to NOSCRIPT errors.

Variations

  1. Use the real redis-py library with a local Redis server for integration testing.
  2. Implement a more complete Lua interpreter using a package like lupa for advanced scripting scenarios.

Real-world use cases

  • Unit-testing application logic that depends on Redis atomic Lua scripts without requiring a live Redis instance.
  • Simulating Redis behavior in CI/CD pipelines where Redis is not available, ensuring consistent testing.
  • Prototyping atomic operations like rate limiters or distributed locks before deploying to production.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Caching & Redis

Related tutorials and quizzes for this topic.