How to access public attributes in RPyC on nested data?

1k Views Asked by At

I am trying access public attributes on RPyC call by following this document but don't see it to be working as mentioned in document.

Documentation says if you don't specify protocol_config={'allow_public_attrs': True,} , public attributes , even of builtin data types won't be accessible. However even if we specify this, public attributes of nested data structure is not accessible ?

RPyC Server code.

import pickle
import rpyc

class MyService(rpyc.Service):
    def on_connect(self, conn):
        # code that runs when a connection is created
        # (to init the service, if needed)
        pass

    def on_disconnect(self, conn):
        # code that runs after the connection has already closed
        # (to finalize the service, if needed)
        pass

    def exposed_get_answer(self): # this is an exposed method
        return 42

    exposed_the_real_answer_though = 43     # an exposed attribute

    def get_question(self):  # while this method is not exposed
        return "what is the airspeed velocity of an unladen swallow?"

    def exposed_hello(self, collection):
        print ("Collection is ", collection)
        print ("Collection type is ", type(collection).__name__)
        for item in collection:
            print ("Item type is ", type(item).__name__)
            print(item)

    def exposed_hello2(self, collection):
        for item in collection:
            for key, val in item.items():
                print (key, val)

    def exposed_hello_json(self, collection):
        for item in collection:
            item = json.loads(item)
            for key, val in item.items():
                print (key, val)


if __name__ == "__main__":
    from rpyc.utils.server import ThreadedServer
    t = ThreadedServer(
            MyService(),
            port=3655,
            protocol_config={'allow_public_attrs': True,}
            )
    t.start()

Client Side Calls

>>> import rpyc
>>> rpyc.__version__
(4, 0, 2)
>>> c = rpyc.connect('a.b.c.d', 3655)  ; client=c.root

# Case 1 If data is in nested structure (using builtin data types) , it doesn't work.

>>> data
[{'a': [1, 2], 'b': 'asa'}]
>>> client.hello2(data)
...
AttributeError: cannot access 'items'
========= Remote Traceback (2) =========
Traceback (most recent call last):
  File "/root/lydian.egg/rpyc/core/protocol.py", line 329, in _dispatch_request
    res = self._HANDLERS[handler](self, *args)
  File "/root/lydian.egg/rpyc/core/protocol.py", line 590, in _handle_call
    return obj(*args, **dict(kwargs))
  File "sample.py", line 33, in exposed_hello2
    for key, val in item.items():
  File "/root/lydian.egg/rpyc/core/netref.py", line 159, in __getattr__
    return syncreq(self, consts.HANDLE_GETATTR, name)
  File "/root/lydian.egg/rpyc/core/netref.py", line 75, in syncreq
    return conn.sync_request(handler, proxy, *args)
  File "/root/lydian.egg/rpyc/core/protocol.py", line 471, in sync_request
    return self.async_request(handler, *args, timeout=timeout).value
  File "/root/lydian.egg/rpyc/core/async_.py", line 97, in value
    raise self._obj
_get_exception_class.<locals>.Derived: cannot access 'items'

Case 2 : Workaround, Pass nested data as string using json (poor man's pickle) and decode at server end.

>>> jdata = [json.dumps({'a': [1,2], 'b':"asa"})].  
>>> client.hello_json(jdata) # Prints following at remote endpoint.
a [1, 2]
b asa

Case 3 : Interestingly, at first level builtin items are accessible as in case of hello method. But calling that on nested data is giving error.

>>> client.hello([1,2,3,4])  # Prints following at remote endpoint.
Collection is  [1, 2, 3, 4]
Collection type is  list
Item type is  int
1
Item type is  int
2
Item type is  int
3
Item type is  int
4

I have workaround / solution to the problem (case 2 above) but looking for explanation on why is this not allowed or if it is a bug. Thanks for inputs.

1

There are 1 best solutions below

0
On

The issue is not related to nested data.

Your problem is that you are not allowing public attributes in the client side. The solution is simple:

c = rpyc.connect('a.b.c.d', 3655, config={'allow_public_attrs': True})

Keep in mind that rpyc is a symmetric protocol (see https://rpyc.readthedocs.io/en/latest/docs/services.html#decoupled-services).

In your case, the server tries to access the client's object, so allow_public_attrs must be set in the client side. Actually for your specific example, there is no need to set allow_public_attrs in the server side.

Regarding case 3:

In the line for item in collection:, the server tries to access two fields: collection.__iter__ and collection.__next__. Both of these fields are considered by default as "safe attributes", and this is why you didn't get error there. To inspect the default configuration dictionary in rpyc:

>>> import rpyc
>>> rpyc.core.protocol.DEFAULT_CONFIG