class LuaRunner(object): def __init__(self, keyspace): self._runtime = LuaRuntime(unpack_returned_tuples=True) self._lua_table_type = type(self._runtime.table()) self._redis_obj = RedisLua(keyspace, self._runtime) def run(self, script, keys, argv): self._runtime.execute('KEYS = {%s}' % ', '.join(map(json.dumps, keys))) self._runtime.execute('ARGV = {%s}' % ', '.join(map(json.dumps, argv))) script_function = self._runtime.eval( 'function(redis) {} end'.format(script)) result = script_function(self._redis_obj) return self._convert_lua_types_to_redis_types(result) def _convert_lua_types_to_redis_types(self, result): def convert(value): """ str -> str true -> 1 false -> None number -> int table -> { if 'err' key is present, raise an error else if 'ok' key is present, return its value else convert to a list using the previous rules } Reference: https://github.com/antirez/redis/blob/5b4bec9d336655889641b134791dfdd2adc864cf/src/scripting.c#L273-L340 """ if isinstance(value, self._lua_table_type): if 'err' in value: raise RedisScriptError(value['err']) elif 'ok' in value: return value['ok'] else: return map(convert, value.values()) elif isinstance(value, (tuple, list, set)): return map(convert, value) elif value is True: return 1 elif value is False: return None elif isinstance(value, float): return int(value) else: # assuming string at this point return value return convert(result)
def test_redislua_with_error_pcall(): k = Keyspace() lua_runtime = LuaRuntime(unpack_returned_tuples=True) redis_lua = RedisLua(k, lua_runtime) table = redis_lua.pcall('GET') assert table['err'] == "wrong number of arguments for 'get' command"
def test_redislua_with_command_error_pcall(): k = Keyspace() lua_runtime = LuaRuntime(unpack_returned_tuples=True) redis_lua = RedisLua(k, lua_runtime) table = redis_lua.pcall('cmd_not_found') assert table[ 'err'] == '@user_script: Unknown Redis command called from Lua script'
def test_redislua_with_error_call(): k = Keyspace() lua_runtime = LuaRuntime(unpack_returned_tuples=True) redis_lua = RedisLua(k, lua_runtime) with pytest.raises(RedisScriptError) as exc: redis_lua.call('GET') assert str(exc.value) == "wrong number of arguments for 'get' command"
def _lua_render_template(luastr: str, kwargs=None): """ Renders a Lua template. """ def getter(obj, attr_name): if attr_name.startswith("_"): raise AttributeError("Not allowed to access attribute `{}` of `{}`" .format(attr_name, type(obj).__name__)) return attr_name def setter(obj, attr_name, value): raise AttributeError("Python object attribute setting is forbidden") # the attribute_handlers are probably enough to prevent access eval otherwise lua = LuaRuntime(register_eval=False, unpack_returned_tuples=True, attribute_handlers=(getter, setter)) # execute the sandbox preamble sandbox = lua.execute(sandbox_preamble) # call sandbox.run with `glob.sandbox, code` # and unpack the variables new = {} # HECK for key, val in kwargs.items(): new[key] = lua.table_from(val) _ = sandbox.run(luastr, lua.table_from(new)) if isinstance(_, bool): # idk return NO_RESULT called, result = _ if lupa.lua_type(result) == 'table': # dictify result = dictify_table_recursively(result) return str(result)
def test_redislua_with_command_error_call(): k = Keyspace() lua_runtime = LuaRuntime(unpack_returned_tuples=True) redis_lua = RedisLua(k, lua_runtime) with pytest.raises(RedisScriptError) as exc: redis_lua.call('cmd_not_found') assert str( exc.value ) == '@user_script: Unknown Redis command called from Lua script'
def test_redislua_return_lua_types_pcall(): k = Keyspace() lua_runtime = LuaRuntime(unpack_returned_tuples=True) redis_lua = RedisLua(k, lua_runtime) lua_script = """return {'test', true, false, 10, 20.3, {'another string'}, redis.call('ping')}""" table = redis_lua.pcall('EVAL', lua_script, 0, []) assert table[1] == 'test' assert table[2] == 1 assert table[3] is False assert table[4] == 10 assert table[5] == 20 assert table[6][1] == 'another string' assert table[7] == 'PONG'
def __init__(self, keyspace): self._runtime = LuaRuntime(unpack_returned_tuples=True) self._lua_table_type = type(self._runtime.table()) self._redis_obj = RedisLua(keyspace, self._runtime)