python-microscope / microscope

Python library for control of microscope devices, supporting hardware triggers and distribution of devices over the network for performance and flexibility.
https://www.python-microscope.org
GNU General Public License v3.0
69 stars 41 forks source link

RPC prevents use of isinstance to check interface of remote device #103

Open carandraug opened 5 years ago

carandraug commented 5 years ago

With the addition of a controller device, we will have methods that return other device instances whose interfaces then need to be checked. Something like this:

c = PriorProScanIII()
d = c.get_devices()
motors = [x for x in d if isinstance(x, MotorDevice)]
filterwheels = [x for x in d if isinstance(x, FilterwheelDevice)]

However, if instead we have:

c = Pyro4.Proxy(...) # or microscope.clients.Client
d = c.get_devices() # let's assume  use of autoproxying on the server

Then all elements of d are Pyro proxies and we can't use isintance to find which ones implement which interface. We will need to do this all on the server side, with something like this:

motors = c.get_devices_of_type(MotorDevice)

But to have the code on the client side work, ignoring the proxy issue, we could have have our Interfaces as our own metaclass to overload how isinstance works. This is explained on PEP3119. The details that is not clear there, is that __instancecheck__ needs to be implemented on the metaclass (not on the abstract class that defines the interface). We would need something like this:

import abc

class SomeMeta(abc.ABCMeta):
    def __instancecheck__(cls, inst):
        for m in cls.__abstractmethods__:
            if not hasattr(inst, m):
                return False
        else:
            return True

class SomeABC(metaclass=SomeMeta):
    @abc.abstractmethod
    def nice(self):
        raise NotImplementedError()

class Foo:
    pass

class Bar:
    def nice(self):
        pass

a = Foo()
b = Bar()
print('a isinstance ', isinstance(a, SomeABC))
print('b isinstance ', isinstance(b, SomeABC))