動的ディスパッチは、DeviceClass の公開メソッドを通信 Frame から呼び出せるようにする仕組みである。DeviceClass のメソッドを通信専用 API として書き直す必要はない。ただし「完全透過」は、すべての Python オブジェクトや副作用が通信境界を越えて同一に振る舞うことを保証する意味ではない。
FastApiServer で api_spec=None を指定すると DeviceRouter が public method を動的に公開する。_ で始まるメンバーは公開対象から除外され、dispatch_exclude によって追加の除外を指定できる。静的 property は getter / setter の有無に応じて別経路で転送される。機器固有 Router の実装は動的ディスパッチと併用できる。
adapter.py は JSON 転送と型情報に基づく復元を担う。dataclass 等の型復元には型アノテーションと解決可能な型定義が必要であり、復元不能な値は元の転送表現が残る場合がある。
grpc_server.py の Describe でメソッド情報を提供し、grpc_client.py がその情報を使って呼び出しメソッドを結び付ける。実際の呼び出しは Call、ストリームは StreamCall を使用する。これは FastAPI のルーティングや OpenAPI 定義と同一の仕組みではない。
ese774 Frame は Pydantic と ApiSpec による明示的 API を扱う。公開する要求・応答のモデル、API パス、入力検証が必要な場合に利用できる。自動ディスパッチと機器固有 Router、明示 API は構成に応じて組み合わせる。
公開対象を DeviceClass のメソッドから自動的に取得する。ApiSpec や Pydantic モデルを個々のメソッドについて定義する必要はない。
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, # 自動ディスパッチ
device_kwargs={},
object_name="temperature",
)
これは構成例であり、server を起動するコードは省略した。DeviceClass には HTTP の処理を記述していない。クライアント側は SyncDeviceClient / AsyncDeviceClient を継承して、必要な独自処理を追加できる。具体的なサーバー側 Router とクライアント側オーバーライドの実例は開発ガイドに示す。
COBOTTA の server_fastapi/spec_ctrl.py は ApiSpec のリストとして公開操作を定義している。要求・応答モデルは server_fastapi/models/ に分離する。以下は、提供されたCOBOTTAソースに存在する定義を抜粋した例である。
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="cobotta内部の変数を任意に取り出す",
),
ApiSpec(
name="set_var",
object_name="cobotta",
request_model=SetVarDataRequest,
response_model=ResultResponse,
method="post",
description="set_ver(name, id_num, value)",
summary="cobotta内部の変数を任意に与える",
),
]
GetVarDataRequest / SetVarDataRequest はPydanticの要求モデル、ResultResponse は応答モデルである。上の2操作は COBOTTA の実際の多数の API 定義からの抜粋であり、完全な仕様一覧ではない。モデルのフィールドは models/var.py で定義する。サーバー側では CobottaRouterCtrl(DeviceRouter) が追加の要求・応答処理を実装し、クライアント側では SyncCobottaClient(SyncDeviceClient)、AsyncCobottaClient(AsyncDeviceClient) が cobotta_ctrl_api_spec を渡している。
この方式では API の名前、HTTP メソッド、入力モデル、出力モデルを明示する。DeviceClass の機器制御ロジックを Pydantic モデルや Router に移動するわけではない。
gRPC Frame は自動ディスパッチのみを使用する。DeviceClass と通信層を分離する点、必要なサーバー側・クライアント側の拡張を配置する点は共通するが、上記の ApiSpec を gRPC 用に記述する必要はない。
.pyiDeviceProxy は Frame に対応したクライアントを登録名から生成するための入口であり、DeviceClass 自体を通信対応クラスへ変えるものではない。.pyi 生成は IDE の補完・型情報のための仕組みであり、通信プロトコルや実行時 API を変更しない。生成する型情報は DeviceClass の公開 API と一致させる必要がある。
DeviceClass の機器固有エラー処理と、Frame によるネットワーク越しのエラー伝達を分けて設計する。クライアントが async でも装置の同時実行は保証されない。公開 API の選択、排他制御、タイムアウトは各機器の要件に合わせて確認する。
旧版は現行の実装仕様ではありません。 旧版の記事