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

Add a dispatcher for a third party package #105

Open
wigging opened this issue Jan 28, 2025 · 1 comment
Open

Add a dispatcher for a third party package #105

wigging opened this issue Jan 28, 2025 · 1 comment

Comments

@wigging
Copy link

wigging commented Jan 28, 2025

I followed the thirdpartylib example to create a Pyro5 interface with a third party Python package. My client, package, and server code is shown below. The library.py code will eventually be replaced with an actual Python package. The purpose of this code is to use the client class to send and receive data from the server. The server interacts with the third party package to send data back to the client.

Instead of sending/receiving data directly from the server. I would like to add a dispatcher class like in the blob-dispatch example so the data is not serialized/deserialized. However, the blob-dispatch example only demonstrates how to send a blob to the server (listener), it does not show how to send data back to the client.

So my question is how can I implement a dispatcher in this example that sends a blob to the server but also sends a blob back to the client?

# client.py

import Pyro5.api

class Client:
    def __init__(self, uri):
        self.library = Pyro5.api.Proxy(uri)

    def get_serial_number(self):
        s = self.library.serial_number()
        return s

    def calculate_motor_temp(self, speed):
        temp = self.library.motor_temperature(speed)
        return temp

    def get_specs(self):
        specs = self.library.specs()
        return specs
# library.py

def serial_number() -> str:
    """Serial number of the device."""
    s = "Ard2342010101"
    return s

def motor_temperature(speed: float) -> float:
    """Calculate motor temperature based on speed."""
    temp = speed / 32.1 * 10
    return temp

def specs() -> dict:
    """Device specifications."""
    spec = {"id": 5987234, "model": "Ford", "year": 1942}
    return spec
# server.py

from Pyro5.api import expose, Daemon
import library

class LibraryAdapter:
    def __init__(self):
        print("[Server] Initialize library adapter")

    @expose
    def serial_number(self):
        s = library.serial_number()
        return s

    @expose
    def motor_temperature(self, speed):
        temp = library.motor_temperature(speed)
        return temp

    @expose
    def specs(self):
        spec = library.specs()
        return spec

def main():
    """Run the server."""

    with Daemon() as daemon:
        uri = daemon.register(LibraryAdapter, "example.thirdpartylib")
        print("[Server] Adapter class registered, uri: ", uri)
        daemon.requestLoop()

if __name__ == "__main__":
    main()

Here is a diagram of what I'm trying to accomplish with Pyro5. In this particular example, the client requests a serial number provided by a third party library or package. The client requests the serial number from the dispatcher. The dispatcher asks the server for the serial number which gets the serial number from the library/package. Finally, the server sends the serial number back to the dispatcher which then sends it back to the client. I would like the dispatcher to behave like the one in the blob-dispatch example where data is passed around using the SerializedBlob class.

Image

@wigging
Copy link
Author

wigging commented Jan 28, 2025

Here is my failed attempt at solving this where I tried sending the serial number back to the client. The library.py code is the same as shown above in the original issue description. Below is the code for the client, custom data object, dispatcher, and server.

# client.py

import Pyro5.api
from custom_data import CustomData

Pyro5.api.register_dict_to_class(CustomData.serialized_classname, CustomData.from_dict)

class Client:
    def __init__(self, uri):
        self.dispatcher = Pyro5.api.Proxy(uri)

    def get_serial_number(self):
        blob = self.dispatcher.serial_number()
        customdata = blob.deserialized()
        s = customdata.serial_num
        return s

def main():
    """Run the client."""

    uri = "PYRO:example.blobdispatcher@localhost:65361"

    client = Client(uri)
    serial = client.get_serial_number()

    print(f"Serial number = {serial}")

if __name__ == "__main__":
    main()
# custom_data.py

class CustomData(object):
    serialized_classname = "blobdispatch.CustomData"

    def __init__(self, temp):
        self.serial_num: str = ""
        self.motor_temp: float = temp
        self.spec: dict = {}

    def to_dict(self):
        """for (serpent) serialization"""
        return {
            "__class__": self.serialized_classname,
            "serial_num": self.serial_num,
            "motor_temp": self.motor_temp,
            "spec": self.spec
        }

    @classmethod
    def from_dict(cls, classname, d):
        """for (serpent) deserialization"""
        assert classname == cls.serialized_classname
        obj = cls(d["motor_temp"])
        return obj
# dispatcher.py

from Pyro5.api import behavior, expose, serve

@behavior(instance_mode="single")
class Dispatcher:
    def __init__(self):
        self.adapter = {}

    @expose
    def register(self, adapter):
        self.adapter = adapter
        print(f"[Dispatcher] New adapter registered: {adapter._pyroUri}")

    @expose
    def serial_number(self):
        return self.adapter.serial_number

serve({Dispatcher: "example.blobdispatcher"}, use_ns=False)
# server.py

import library
from Pyro5.api import expose, Proxy, register_dict_to_class, Daemon
from custom_data import CustomData

register_dict_to_class(CustomData.serialized_classname, CustomData.from_dict)

class LibraryAdapter:
    def __init__(self):
        print("[Server] initialized library adapter")

    def register_with_dispatcher(self, uri):
        with Proxy(uri) as dispatcher:
            dispatcher.register(self)

    @expose
    def serial_number(self):
        s = library.serial_number()
        return s

    @expose
    def motor_temperature(self, speed):
        temp = library.motor_temperature(speed)
        return temp

    @expose
    def specs(self):
        spec = library.specs()
        return spec

def main():
    """Run the server."""
    uri = "PYRO:example.blobdispatcher@localhost:65361"

    adapter = LibraryAdapter()
    daemon = Daemon()
    daemon.register(adapter, "example.blobdispatcher")

    adapter.register_with_dispatcher(uri)
    print("[Server] registered adapter with dispatcher")

    daemon.requestLoop()

if __name__ == "__main__":
    main()

I don't get any errors when I run the dispatcher and server. But when I run the client I get the following errors:

Traceback (most recent call last):
  File "/Users/homer/Desktop/pyro5-examples/third-party-dispatch/client.py", line 42, in <module>
    main()
    ~~~~^^
  File "/Users/homer/Desktop/pyro5-examples/third-party-dispatch/client.py", line 32, in main
    serial = client.get_serial_number()
  File "/Users/homer/Desktop/pyro5-examples/third-party-dispatch/client.py", line 20, in get_serial_number
    blob = self.dispatcher.serial_number()
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/client.py", line 510, in __call__
    return self.__send(self.__name, args, kwargs)
           ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/client.py", line 268, in _pyroInvoke
    data = serializer.loads(msg.data)
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/serializers.py", line 290, in loads
    return self.recreate_classes(serpent.loads(data))
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/serializers.py", line 256, in recreate_classes
    return self.dict_to_class(literal)
           ~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/serializers.py", line 309, in dict_to_class
    return super(SerpentSerializer, cls).dict_to_class(data)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/homer/Desktop/pyro5-examples/.venv/lib/python3.13/site-packages/Pyro5/serializers.py", line 235, in dict_to_class
    raise errors.SerializeError("unsupported serialized class: " + classname)
Pyro5.errors.SerializeError: unsupported serialized class: Pyro5.client._RemoteMethod

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

1 participant