irmen / Pyro4

Pyro 4.x - Python remote objects
http://pyro4.readthedocs.io/
MIT License
713 stars 83 forks source link

Client Lua - Json Serializer #66

Closed alexsilva closed 9 years ago

alexsilva commented 9 years ago

I am developing a client on the Lua 3.2 programming language. As will be used in a legacy project, the Lua version being used is 3.2

By defining a simple API like this

class Math(object):
    def sum(self, a, b):
        return a + b

When calling the API client in the Lua

local px = proxy('PYRO:obj_5d871322110a4062b25d7a21bc34ae8e@localhost:59249')

px:start_connection()

local a, b = 2, 3
local result = px.sum(2, 3)

if type(result) == 'number' then
    println(format("%d + %d = %d", a, b, result))
else
    tprint(result)
end

Using json serializer, I have to pass parameters of type 'kwargs' for remote function. Otherwise I get an error

exceptions.KeyError
...
self.loadsCall(data)
4: File "C:\Python27\lib\site-packages\Pyro4\util.py", line 494, in loadsCall kwargs = self.recreate_classes(data["kwargs"])
5: KeyError: 'kwargs'
__exception__: true
args: {
n: 1
1: kwargs
}

When calling the api with any parameter: https://github.com/alexsilva/Pyrolite/blob/lua-dev/lua32/core.lua#L55

local data = serializer:dumps({
        object= self.uri.objectid,
        method = methodname,
        params = args,
        kwargs = { x =1 }
    })

I get a new error:

__exception__: true
args: {
n: 1
1: sum() got an unexpected keyword argument 'x'
}

You can see that there is a problem in json serializer here: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/util.py#L494

kwargs = self.recreate_classes(data["kwargs"])

This could be written this way:

if  "kwargs" in data:
    kwargs = self.recreate_classes(data["kwargs"])
else:
   kwargs = {}

Because even without declaring 'kwargs' in function, is valid for the python:

>>> def x():
...     pass
...     
>>> d = {}
>>> x(**d)
alexsilva commented 9 years ago

Fix: https://github.com/irmen/Pyro4/pull/67

irmen commented 9 years ago

I think Pyro is not at fault here, it is your proxy that is not working correctly.

See here https://github.com/alexsilva/Pyrolite/blob/lua-dev/lua32/core.lua#L60 you are only passing a kwargs if there are any provided in the method invocation.

Pyro expects a proxy to always pass a kwargs to the underlying Pyro machinery: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L169 because that call is handled as follows: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L351 https://github.com/irmen/Pyro4/blob/master/src/Pyro4/util.py#L167

In other words, fix your Lua proxy to always pass a kwargs -- it being an empty dictionary if the method call didn't have any kwargs. Same thing for the vargs -- it needs to be there as empty tuple if your method call didn't receive any vargs

For now, I'm rejecting the pull request.

alexsilva commented 9 years ago

You do not understand. The problem is not in pyro logic, but in the serializer. Fortunately I can rewrite the serializer and solve the problem.

Enviado por Samsung Mobile.

-------- Mensagem original --------
De : Irmen de Jong notifications@github.com
Data:26/12/2014 09:09 (GMT-03:00)
Para: irmen/Pyro4 Pyro4@noreply.github.com
Cc: Alex Sandro geniofuturo@gmail.com
Assunto: Re: [Pyro4] Client Lua - Json Serializer (#66)
I think Pyro is not at fault here, it is your proxy that is not working correctly. See here https://github.com/alexsilva/Pyrolite/blob/lua-dev/lua32/core.lua#L60 you are only passing a kwargs if there are any provided in the method invocation. Pyro expects a proxy to always pass a kwargs to the underlying Pyro machinery: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L169 because that call is handled as follows: https://github.com/irmen/Pyro4/blob/master/src/Pyro4/core.py#L351 https://github.com/irmen/Pyro4/blob/master/src/Pyro4/util.py#L167 In other words, fix your Lua proxy to always pass a kwargs -- it being an empty dictionary if the method call didn't have any kwargs. Same thing for the vargs -- it needs to be there as empty tuple if your method call didn't receive any vargs For now, I'm rejecting the pull request. — Reply to this email directly or view it on GitHub.
alexsilva commented 9 years ago

Lua json

{"object": "obj_d2a02cb32165424b828d13189098da81","params": [2],"method": "pow","kwargs": []}

The main type of Lua is the table: {}

A table in json is equivalent to a python list: {} == []

The result of passing empty kwargs is this:

__class__: exceptions.TypeError
args: {
1: pow() argument after ** must be a mapping, not list
...

So I suggested this change:

    def loadsCall(self, data):
        data = data.decode("utf-8")
        data = json.loads(data)
        vargs = self.recreate_classes(data["params"])

       # The client did not pass kwargs (because it is not necessary).

        if 'kwargs' in data:
            kwargs = self.recreate_classes(data["kwargs"])
        else:
            kwargs = {}

        return data["object"], data["method"], vargs, kwargs
irmen commented 9 years ago

Sorry, but "lua json" makes no sense. Json is a language neutral serialization format.

The change you're suggesting is a change in the deserialization logic of Pyro. That change is unnecessary unless you're passing in invalid Json. And that is what I tried to tell you: your lua code is constructing a Json message that doesn't conform to what Pyro expects. It is an error in your lua code, not in Pyro's json deserializer.

The error message is actually saying exactly what is wrong: the value of the kwargs item in your json message is a list, instead of a mapping! For instance this is what I copied from an actual pyro log when invoking the name server's ping method via json:

{"params": [], "method": "ping", "kwargs": {}, "object": "Pyro.NameServer"}'

Note the {} value for kwargs, it's not [] !

It's actually very straightforward, Python uses tuple for vargs and dict for kwargs and that is what your json message needs to reflect as well:

>>> def x(*vargs, **kwargs):
...   print("vargs={0}  kwargs={1}".format(vargs, kwargs))
...
>>> x()
vargs=()  kwargs={}
>>>
irmen commented 9 years ago

By testing your issue I did uncover a different problem with the json serializer in Pyro, issue #68 . This is unrelated, and fixed now :)

alexsilva commented 9 years ago

I adjusted the json serializer return the correct object (required for pyro4). https://github.com/alexsilva/Pyrolite/blob/lua-dev/lua32/serializers/json.lua#L35

A new change in the code. https://github.com/alexsilva/Pyrolite/blob/lua-dev/lua32/core.lua#L46

I could already conclude that Pyro4 is excellent.

An application Lua will make use of an api in a Django project (site). My initial tests have shown excellent results.

Server implementation: http://pastebin.com/NDzLsq01

Django command: http://pastebin.com/gckedeND

irmen commented 9 years ago

Glad to hear you got it working!