Ohara Lab

Using DeviceProxy and Factory

Back to development guide

Scope

The implementation provides three distinct implementations: ese774_frame.clients.device_proxy, grpc_frame.device_proxy, and the frame-independent device_proxy_factory (core.py, factories.py, typing_generator.py). These construct clients; they do not start a device server.

ese774 Frame DeviceProxy

Register the device name and client classes, then construct a client. Replace placeholder imports with the actual device package classes.

from ese774_frame.clients.device_proxy import DeviceProxy, register_device_proxy
from your_device.clients import ExampleAsyncClient, ExampleSyncClient

register_device_proxy("ExampleDevice", async_client_cls=ExampleAsyncClient,
                      sync_client_cls=ExampleSyncClient, default_async_mode=True)
client = DeviceProxy("ExampleDevice", "http://127.0.0.1:8000", async_mode=False)

async_mode=None uses the registered default. If no specialized client is registered for the selected mode, the implementation falls back to SyncDeviceClient / AsyncDeviceClient, passing api_spec and resolved object_name. create_device_proxy("ExampleDevice") returns a callable with the device name fixed:

from ese774_frame.clients.device_proxy import create_device_proxy
ExampleProxy = create_device_proxy("ExampleDevice")
client = ExampleProxy("http://127.0.0.1:8000", async_mode=False)

The address is illustrative; actual constructor arguments depend on the registered client.

gRPC Frame DeviceProxy

The gRPC Frame maintains a separate registry from ese774 Frame.

from grpc_frame.device_proxy import DeviceProxy, register_device_proxy
from your_device.grpc_clients import ExampleAsyncGrpcClient, ExampleSyncGrpcClient

register_device_proxy("ExampleDevice", async_client_cls=ExampleAsyncGrpcClient,
                      sync_client_cls=ExampleSyncGrpcClient, default_async_mode=False)
client = DeviceProxy("ExampleDevice", "127.0.0.1:50051", async_mode=False)

The gRPC implementation uses Describe for runtime method discovery, not api_spec. It falls back to SyncGrpcClient / AsyncGrpcClient if the selected specialized client is absent. Registration requires a specialized class or api_spec. Actual connection arguments depend on the client implementation.

Frame-independent Factory (experimental)

The provided device_proxy_factory.core.DeviceProxy is not the same function as either Frame’s DeviceProxy. It forwards arguments unchanged to a registered creator. The common registry does not interpret async_mode or select transport protocols.

The following reproduces the logic of the supplied multiple_devices.py without hardware:

from device_proxy_factory import DeviceProxy, register_device_proxy, make_async_sync_factory

class SyncExampleClient:
    def __init__(self, address):
        self.address = address

class AsyncExampleClient(SyncExampleClient):
    pass

class TangoExampleClient:
    def __init__(self, url):
        self.url = url

register_device_proxy("ExampleHttp", make_async_sync_factory(
    async_client_cls=AsyncExampleClient, sync_client_cls=SyncExampleClient))
register_device_proxy("ExampleTango", TangoExampleClient)

http = DeviceProxy("ExampleHttp", "127.0.0.1", async_mode=False)
tango = DeviceProxy("ExampleTango", "tango://example")

make_async_sync_factory() interprets async_mode, selecting the configured default when None. Missing selected client classes raise ValueError; invalid async_mode types raise TypeError. The common registry rejects duplicate names with ValueError and missing names raise KeyError. It does not automatically share registrations with either Frame.

create_device_proxy(device_class) fixes the device name in a local wrapper; it does not implement transport.

Typing (.pyi)

typing_generator.py generates core.pyi from an explicit JSON manifest, not by introspecting the runtime registry:

{
  "devices": [
    {"device_class": "ExampleHttp", "async_client": "your_device.clients.AsyncExampleClient",
     "sync_client": "your_device.clients.SyncExampleClient", "default_async_mode": true},
    {"device_class": "ExampleTango", "client": "your_device.clients.TangoExampleClient"}
  ]
}
python -m device_proxy_factory.typing_generator devices.json path/to/core.pyi

The generator uses Literal / overload for device-name-specific return types. It does not copy device-specific .pyi files. The supplied generic core.pyi has no device-specific overloads.

Validation before deployment

The common Factory has no deployment history. Its registration lifecycle, duplicate handling, stub placement, async selection, connection failures, and integration with real Frame clients require validation. It does not unify transport semantics, device APIs, or error propagation; those remain the responsibility of the respective clients and Frames.