Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mockredis is not Python 3-compatible when using Lua #130

Open
beamerblvd opened this issue Oct 30, 2017 · 4 comments
Open

Mockredis is not Python 3-compatible when using Lua #130

beamerblvd opened this issue Oct 30, 2017 · 4 comments

Comments

@beamerblvd
Copy link

Mockredis claims to be Python 3-compatible on the README, and perhaps it is for many uses cases, but it depends on a very old python-lunatic-bugfix library that has not be updated in years and is not Python 3 compatible, so there's no way to use Mockredis with Lua and Python 3. Including mockredispy[lua]~=2.9 in my setup.py results in this error:

Collecting lunatic-python-bugfix==1.1.1 (from mockredispy[lua]~=2.9->pysoa==0.18.1)
  Using cached lunatic-python-bugfix-1.1.1.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/kf/1sq3029d6v3bvtg6066zxwyh0000gp/T/pip-build-nx1ghdoa/lunatic-python-bugfix/setup.py", line 5, in <module>
        import commands
    ModuleNotFoundError: No module named 'commands'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/kf/1sq3029d6v3bvtg6066zxwyh0000gp/T/pip-build-nx1ghdoa/lunatic-python-bugfix/

Need an update to a Python 3-compatible library. This author has forked the Lunatic project and kept it up-to-date, but doesn't seem interested in publishing to PyPi. He has opened a solicitation for transfer of ownership, and perhaps you would be willing to take ownership and publish it?

In the meantime, I am looking at forking it for the purposes of publishing once to PyPi, but I can't commit the time necessary to take ownership.

@beamerblvd
Copy link
Author

Another issue I encountered that makes it further not-Python-3-compatible:

    def _python_to_lua(oval):
        ...
        elif isinstance(pval, str):
            # Python string --> Lua userdata
            return pval
        elif isinstance(pval, bool):
            # Python bool--> Lua boolean
            return lua.eval(str(pval).lower())
        elif isinstance(pval, (int, long, float)):

Does not account for unicode strings, blindly uses long, which doesn't exist in Python 3.

@beamerblvd
Copy link
Author

Hopefully this will help: In order to get this to work, I had to install that other Lunatic dependency, and make the following changes to mockredis.script.Script:

def _execute_lua(self, keys, args, client):
    """
    Patch MockRedis+Lua for error_reply
    """
    lua, lua_globals = Script._import_lua(self.load_dependencies)
    lua_globals.KEYS = self._python_to_lua(keys)
    lua_globals.ARGV = self._python_to_lua(args)

    def _call(*call_args):
        # redis-py and native redis commands are mostly compatible argument
        # wise, but some exceptions need to be handled here:
        if str(call_args[0]).lower() == 'lrem':
            response = client.call(
                call_args[0], call_args[1],
                call_args[3],  # "count", default is 0
                call_args[2])
        else:
            response = client.call(*call_args)
        return self._python_to_lua(response)

    def _error_reply(err):
        return self._python_to_lua({'err': err})

    lua_globals.redis = {'call': _call, 'error_reply': _error_reply}
    return self._lua_to_python(lua.execute(self.script), return_status=True)


# noinspection PyDecorator
@staticmethod
def _lua_to_python(lval, return_status=False):
    """
    Patch MockRedis+Lua for Python 3 compatibility
    """
    # noinspection PyUnresolvedReferences
    import lua
    lua_globals = lua.globals()
    if lval is None:
        # Lua None --> Python None
        return None
    if lua_globals.type(lval) == "table":
        # Lua table --> Python list
        pval = []
        for i in lval:
            if return_status:
                if i == 'ok':
                    return lval[i]
                if i == 'err':
                    raise ResponseError(lval[i])
                if lval[i] == 'err':
                    raise ResponseError(lval[i + 1])
            pval.append(Script._lua_to_python(lval[i]))
        return pval
    elif isinstance(lval, six.integer_types):
        # Lua number --> Python long
        return six.integer_types[-1](lval)
    elif isinstance(lval, float):
        # Lua number --> Python float
        return float(lval)
    elif lua_globals.type(lval) == "userdata":
        # Lua userdata --> Python string
        return str(lval)
    elif lua_globals.type(lval) == "string":
        # Lua string --> Python string
        return lval
    elif lua_globals.type(lval) == "boolean":
        # Lua boolean --> Python bool
        return bool(lval)
    raise RuntimeError("Invalid Lua type: " + str(lua_globals.type(lval)))


# noinspection PyDecorator
@staticmethod
def _python_to_lua(pval):
    """
    Patch MockRedis+Lua for Python 3 compatibility
    """
    # noinspection PyUnresolvedReferences
    import lua
    if pval is None:
        # Python None --> Lua None
        return lua.eval("")
    if isinstance(pval, (list, tuple, set)):
        # Python list --> Lua table
        # e.g.: in lrange
        #     in Python returns: [v1, v2, v3]
        #     in Lua returns: {v1, v2, v3}
        lua_list = lua.eval("{}")
        lua_table = lua.eval("table")
        for item in pval:
            lua_table.insert(lua_list, Script._python_to_lua(item))
        return lua_list
    elif isinstance(pval, dict):
        # Python dict --> Lua dict
        # e.g.: in hgetall
        #     in Python returns: {k1:v1, k2:v2, k3:v3}
        #     in Lua returns: {k1, v1, k2, v2, k3, v3}
        lua_dict = lua.eval("{}")
        lua_table = lua.eval("table")
        for k, v in six.iteritems(pval):
            lua_table.insert(lua_dict, Script._python_to_lua(k))
            lua_table.insert(lua_dict, Script._python_to_lua(v))
        return lua_dict
    elif isinstance(pval, tuple(set(six.string_types + (six.binary_type, )))):
        # Python string --> Lua userdata
        return pval
    elif isinstance(pval, bool):
        # Python bool--> Lua boolean
        return lua.eval(str(pval).lower())
    elif isinstance(pval, six.integer_types + (float, )):
        # Python int --> Lua number
        lua_globals = lua.globals()
        return lua_globals.tonumber(str(pval))

    raise RuntimeError("Invalid Python type: " + str(type(pval)))

I will try to post a pull request tonight.

@beamerblvd
Copy link
Author

Submitted pull request #133.

@advance512
Copy link

For people having issue with this..

We just moved to fakeredis. Pretty simple, and a drop in replacement.

Just do:
pip install fakeredis[lua]==0.16.0

and then:

`from fakeredis import FakeStrictRedis`

@mock.patch('redis.Redis', FakeStrictRedis)
def .......etcetc

Simple. I hope Location Labs wake up at some point..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants