チュートリアル
このセクションでは、v0.6.1 時点の ese774_frame の基本的な使い方を説明する。
現在は api_spec=None の完全自動 dispatch を基本形とする。Pydantic request model と ApiSpec は必須ではなく、明示的な
HTTP/OpenAPI 契約が必要な場合に使用する。
フレームに渡す制御クラスを用意する
#!/usr/bin/env python
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
@dataclass
class SimpleStatus:
name: str
counter: int
class SimpleCtrl:
"""フレームワークテスト用の最小 Ctrl。"""
def __init__(self, name: str = "simple", logger: Optional[Any] = None):
self._name = name
self._counter = 0
self._logger = logger
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str) -> None:
self._name = value
def ping(self) -> str:
return "pong"
def add(self, a: int, b: int) -> int:
return a + b
def echo(self, msg: str) -> str:
return msg
def sum_list(self, values: List[float]) -> float:
return float(sum(values))
def mix(
self,
a: int,
b: int = 1,
*,
scale: float = 1.0,
tag: Optional[str] = None,
) -> Dict[str, Any]:
val = (a + b) * scale
return {"value": val, "tag": tag}
def make_tuple(self, a: int, b: str) -> Tuple[int, str]:
return (a, b)
def make_dict(self, key: str, value: Any) -> Dict[str, Any]:
return {key: value}
def maybe(self, x: Optional[int] = None) -> Optional[int]:
return x
def get_state(self) -> SimpleStatus:
self._counter += 1
return SimpleStatus(name=self._name, counter=self._counter)
def general(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
return {"args": list(args), "kwargs": dict(kwargs)}
public method と static property は完全自動 dispatch の公開対象になる。_ で始まるメンバは公開対象にしない。
SimpleStatus のような dataclass も、戻り値アノテーションが付いていれば通信後にクライアント側で復元できる。
サーバーを起動する
基本形では Pydantic model、ApiSpec、機器固有 Router は不要である。
#!/usr/bin/env python
from ese774_frame.api_server import FastApiServer
from ctrl import SimpleCtrl
from x_logger.x_logger import XLogger
import logging
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
if __name__ == "__main__":
logger = XLogger(log_level="debug", logger_name="SimpleServer")
server = FastApiServer(
device_cls=SimpleCtrl,
router_cls=None,
config=None,
api_spec=None,
device_kwargs={"name": "simple"},
logger=logger,
logger_name="SimpleServer",
lifespan_msg_prefix="SIMPLE",
object_name="simple",
)
server.run(host="127.0.0.1", port=8000)
router_cls=None かつ api_spec=None の場合、Framework 標準の DeviceRouter が使用される。
公開したくない public API がある場合は dispatch_exclude を指定する。
server = FastApiServer(
device_cls=SimpleCtrl,
router_cls=None,
config=None,
api_spec=None,
object_name="simple",
dispatch_exclude=["general"],
)
クライアントから呼び出す
Sync client では通常の Python method に近い形で呼び出せる。
from ese774_frame.clients import SyncDeviceClient
client = SyncDeviceClient(
server_ip="127.0.0.1",
server_port=8000,
object_name="simple",
)
print(client.ping())
print(client.add(1, 2))
print(client.get_state())
print(client.name)
client.name = "renamed"
print(client.name)
Async client では method 呼び出しを await する。
from ese774_frame.clients import AsyncDeviceClient
client = AsyncDeviceClient(
server_ip="127.0.0.1",
server_port=8000,
object_name="simple",
)
print(await client.ping())
print(await client.add(1, 2))
print(await client.get_state())
Async client でも property は通常の属性アクセスとして扱う。
print(client.name)
client.name = "renamed"
client 用 pyi を生成する
完全自動 dispatch は実行時に API を解決するため、そのままでは IDE が device 固有 method の型を静的に推論できない。
make_pyi_device_client() に device_class を渡すと、制御クラスの public method/property から stub を生成できる。
#!/usr/bin/env python
from pathlib import Path
from ese774_frame.clients.make_pyi_device_client import make_pyi_device_client
from ctrl import SimpleCtrl
def main() -> None:
root = Path(__file__).resolve().parent
make_pyi_device_client(
filename=str(root / "clients" / "async_simple_client.pyi"),
class_name="AsyncSimpleClient",
async_mode=True,
device_class=SimpleCtrl,
)
make_pyi_device_client(
filename=str(root / "clients" / "sync_simple_client.pyi"),
class_name="SyncSimpleClient",
async_mode=False,
device_class=SimpleCtrl,
)
if __name__ == "__main__":
main()
api_spec と device_class は同時には指定しない。
完全自動 dispatch では Router 自体の API は実行時に解決されるため、従来の make_pyi_device_router() をこの用途で生成する必要はない。
make_pyi_device_router() は ApiSpec モード用である。
DeviceProxy を登録する
機器パッケージから統一的な生成入口を公開したい場合は register_device_proxy() を使用する。
完全自動 dispatch では api_spec=None とし、サーバーと同じ object_name を登録する。
from ese774_frame import DeviceProxy
from ese774_frame.clients import register_device_proxy
register_device_proxy(
"SimpleCtrl",
api_spec=None,
object_name="simple",
default_async_mode=False,
aliases=["simple"],
)
__all__ = [
"DeviceProxy",
]
利用側では次のように生成する。
from server import DeviceProxy
client = DeviceProxy(
"SimpleCtrl",
server_ip="127.0.0.1",
server_port=8000,
async_mode=False,
)
print(client.ping())
print(client.add(1, 2))
print(client.get_state())
機器固有の Sync / Async client class が必要な場合は、register_device_proxy() の sync_client_cls / async_client_cls
に登録できる。未指定の場合は Framework 標準 client が使用される。
機器固有 Router が必要な場合
一部 API だけサーバー側で特殊処理を行う場合は、DeviceRouter を継承した Router を指定する。
from ese774_frame.routers import DeviceRouter
class SimpleRouter(DeviceRouter):
pass
server = FastApiServer(
device_cls=SimpleCtrl,
router_cls=SimpleRouter,
config=None,
api_spec=None,
object_name="simple",
)
Router 側で override されていない API は device class の完全自動 dispatch にフォールバックする。
ApiSpec / Pydantic モードを使う場合
明示的な request/response schema や OpenAPI 契約が必要な場合は、従来どおり Pydantic model と ApiSpec を定義する。
model
#!/usr/bin/env python
from pydantic import BaseModel
class AddRequest(BaseModel):
a: int
b: int
spec
#!/usr/bin/env python
from ese774_frame.models.api_spec import ApiSpec
from server.models import AddRequest
simple_api_spec = [
ApiSpec(
name="add",
object_name="simple",
request_model=AddRequest,
response_model=None,
method="post",
summary="add",
description="a + b",
),
]
property を ApiSpec で公開する場合は kind="property" を指定する。
ApiSpec(
name="name",
object_name="simple",
request_model=None,
response_model=str,
kind="property",
writable=False,
)
ApiSpec モードでは router_cls が必要である。
from ese774_frame.api_server import FastApiServer
from ese774_frame.routers.device_router import DeviceRouter
server = FastApiServer(
device_cls=SimpleCtrl,
router_cls=DeviceRouter,
config=None,
api_spec=simple_api_spec,
object_name="simple",
)
client / router の .pyi は ApiSpec から生成できる。
from ese774_frame.clients.make_pyi_device_client import make_pyi_device_client
from ese774_frame.routers.make_pyi_device_router import make_pyi_device_router
make_pyi_device_client(
filename="sync_simple_client.pyi",
api_spec=simple_api_spec,
class_name="SyncSimpleClient",
async_mode=False,
)
make_pyi_device_router(
filename="simple_router.pyi",
api_spec=simple_api_spec,
class_name="SimpleRouter",
)
作者
Kengo NAKADA