Dynamic dispatch exposes DeviceClass methods through a communication Frame without rewriting them as transport-specific endpoints. “Transparent” does not imply that every Python object or side effect behaves identically across a network boundary.
With api_spec=None, FastApiServer uses DeviceRouter to expose public methods. Names beginning with _ are excluded, and dispatch_exclude provides additional exclusions. Static properties use separate getter/setter transport. Device-specific Routers can coexist with automatic dispatch. adapter.py serializes JSON and reconstructs supported typed return values using available annotations; reconstruction is not guaranteed for arbitrary objects.
grpc_server.py exposes Describe, Call, and StreamCall. grpc_client.py binds remote methods from the Describe response. This is distinct from FastAPI routing and OpenAPI definitions.
ese774 supports explicit APIs through Pydantic models and ApiSpec, including defined request/response models and API paths. Explicit routes and automatic dispatch can be combined where appropriate.
The server discovers public DeviceClass methods without requiring a Pydantic model or ApiSpec for each method.
from ese774_frame.api_server import FastApiServer
class TemperatureCtrl:
def __init__(self, logger=None):
self._temperature = 25.0
def read_temperature(self) -> float:
return self._temperature
def set_temperature(self, value: float) -> None:
self._temperature = value
server = FastApiServer(
device_cls=TemperatureCtrl,
router_cls=None,
config=None,
api_spec=None, # Automatic dispatch
device_kwargs={},
object_name="temperature",
)
This is a configuration example; the server startup call is omitted. The DeviceClass contains no HTTP-specific code. Client subclasses can add operations where necessary. See the development guide for actual server-side router and client-side override examples.
COBOTTA’s server_fastapi/spec_ctrl.py declares operations as a list of ApiSpec objects, with request/response models under server_fastapi/models/. The following is an excerpt of the supplied COBOTTA definitions.
from ese774_frame.models.api_spec import ApiSpec
from cobotta2.server_fastapi.models.common import ResultResponse
from cobotta2.server_fastapi.models.var import GetVarDataRequest, SetVarDataRequest
cobotta_ctrl_api_spec = [
ApiSpec(
name="get_var",
object_name="cobotta",
request_model=GetVarDataRequest,
response_model=ResultResponse,
method="post",
description="get_ver(name, id_num)",
summary="Read an internal COBOTTA variable",
),
ApiSpec(
name="set_var",
object_name="cobotta",
request_model=SetVarDataRequest,
response_model=ResultResponse,
method="post",
description="set_ver(name, id_num, value)",
summary="Write an internal COBOTTA variable",
),
]
GetVarDataRequest and SetVarDataRequest are request models, and ResultResponse is a response model. These are two operations from a larger actual API specification, not its complete definition. Model fields live in models/var.py. On the server, CobottaRouterCtrl(DeviceRouter) adds request/response handling. On the client, SyncCobottaClient(SyncDeviceClient) and AsyncCobottaClient(AsyncDeviceClient) pass cobotta_ctrl_api_spec to their base classes.
This approach explicitly declares operation names, HTTP methods, and input/output models. It does not relocate equipment control logic from the DeviceClass into Pydantic models or routers.
gRPC Frame supports automatic dispatch only. It retains the same separation between DeviceClass, server-side adaptation, and client-side extensions, but does not require the ese774-specific ApiSpec definitions shown above.
.pyiDeviceProxy creates a registered Frame client; it does not turn DeviceClass into a transport-dependent class. Generated .pyi files support static typing and IDE completion; they do not alter runtime transport behavior. Stubs should match the actual exposed DeviceClass API.
Separate device-level error handling from transport-level propagation. Async client methods do not guarantee concurrent device execution. Review exposure, locking, and timeouts for each instrument.
Worked examples from ese774_dummy