From 22b8450c98994b102c87e5bbd79143ce73b4b4f8 Mon Sep 17 00:00:00 2001 From: Peter Braun Date: Wed, 18 Feb 2026 10:24:24 +0100 Subject: [PATCH] shortened parameter and property type annotation --- src/secop_ophyd/GenNodeCode.py | 4 +- src/secop_ophyd/SECoPDevices.py | 4 +- tests/test_annotation.py | 154 ++++++++++++++++++++++++++++++++ tests/test_classgen.py | 46 +++++----- uv.lock | 9 +- 5 files changed, 187 insertions(+), 30 deletions(-) create mode 100644 tests/test_annotation.py diff --git a/src/secop_ophyd/GenNodeCode.py b/src/secop_ophyd/GenNodeCode.py index 1890a06..fdaef66 100644 --- a/src/secop_ophyd/GenNodeCode.py +++ b/src/secop_ophyd/GenNodeCode.py @@ -201,8 +201,8 @@ def __init__(self, path: str | None = None, log=None): self.add_import("ophyd_async.core", "SupersetEnum") self.add_import("typing", "Any") self.add_import("numpy", "ndarray") - self.add_import("secop_ophyd.SECoPDevices", "ParameterType") - self.add_import("secop_ophyd.SECoPDevices", "PropertyType") + self.add_import("secop_ophyd.SECoPDevices", "ParameterType as ParamT") + self.add_import("secop_ophyd.SECoPDevices", "PropertyType as PropT") # Add necessary Device imports self.add_import("secop_ophyd.SECoPDevices", "SECoPDevice") self.add_import("secop_ophyd.SECoPDevices", "SECoPCommunicatorDevice") diff --git a/src/secop_ophyd/SECoPDevices.py b/src/secop_ophyd/SECoPDevices.py index 36441f5..dc8c70f 100644 --- a/src/secop_ophyd/SECoPDevices.py +++ b/src/secop_ophyd/SECoPDevices.py @@ -142,7 +142,7 @@ class ParameterType: def __repr__(self) -> str: """Return repr suitable for code generation in annotations.""" - return "ParameterType()" + return "ParamT()" def __call__(self, parent: Device, child: Device): if not isinstance(child, Signal): @@ -162,7 +162,7 @@ class PropertyType: def __repr__(self) -> str: """Return repr suitable for code generation in annotations.""" - return "PropertyType()" + return "PropT()" def __call__(self, parent: Device, child: Device): if not isinstance(child, Signal): diff --git a/tests/test_annotation.py b/tests/test_annotation.py new file mode 100644 index 0000000..da0f199 --- /dev/null +++ b/tests/test_annotation.py @@ -0,0 +1,154 @@ +# mypy: disable-error-code="attr-defined" + + +from ophyd_async.core import SignalR, init_devices + +from secop_ophyd.SECoPDevices import SECoPMoveableDevice, SECoPNodeDevice + + +async def test_subset_signals_annotation(cryo_sim): + + from typing import Annotated as A + + from numpy import ndarray + from ophyd_async.core import SignalRW + from ophyd_async.core import StandardReadableFormat as Format + from ophyd_async.core import StrictEnum + + from secop_ophyd.SECoPDevices import ParameterType + + class Cryostat_Mode_Enum(StrictEnum): + """mode enum for `Cryostat`.""" + + RAMP = "ramp" + PID = "pid" + OPENLOOP = "openloop" + + class Cryostat(SECoPMoveableDevice): + """A simulated cc cryostat with heat-load, specific heat for the sample and a + temperature dependent heat-link between sample and regulation.""" + + # Module Parameters + value: A[ + SignalR[float], ParameterType(), Format.HINTED_SIGNAL + ] # regulation temperature; Unit: (K) + status: A[SignalR[ndarray], ParameterType()] # current status of the module + target: A[ + SignalRW[float], ParameterType(), Format.HINTED_SIGNAL + ] # target temperature; Unit: (K) + + class Cryo_7_frappy_demo(SECoPNodeDevice): + """short description + + This is a very long description providing all the gory details about the stuff + we are describing. + """ + + # Module Devices + cryo: Cryostat + + async with init_devices(): + cryo = Cryo_7_frappy_demo( + sec_node_uri="localhost:10769", + ) + + val_read = await cryo.cryo.read() + assert val_read != {} + + +async def test_enum_annotation(cryo_sim): + + from typing import Annotated as A + + from numpy import ndarray + from ophyd_async.core import SignalR, SignalRW + from ophyd_async.core import StandardReadableFormat as Format + from ophyd_async.core import StrictEnum + + from secop_ophyd.SECoPDevices import ( + ParameterType, + PropertyType, + SECoPMoveableDevice, + SECoPNodeDevice, + ) + + class Cryostat_Mode_Enum(StrictEnum): + """mode enum for `Cryostat`.""" + + RAMP = "ramp" + PID = "pid" + OPENLOOP = "openloop" + + class Cryostat(SECoPMoveableDevice): + """A simulated cc cryostat with heat-load, specific heat for the sample and a + temperature dependent heat-link between sample and regulation.""" + + # Module Properties + group: A[SignalR[str], PropertyType()] + description: A[SignalR[str], PropertyType()] + implementation: A[SignalR[str], PropertyType()] + interface_classes: A[SignalR[ndarray], PropertyType()] + features: A[SignalR[ndarray], PropertyType()] + + # Module Parameters + value: A[ + SignalR[float], ParameterType(), Format.HINTED_SIGNAL + ] # regulation temperature; Unit: (K) + status: A[SignalR[ndarray], ParameterType()] # current status of the module + target: A[ + SignalRW[float], ParameterType(), Format.HINTED_SIGNAL + ] # target temperature; Unit: (K) + ramp: A[ + SignalRW[float], ParameterType() + ] # ramping speed of the setpoint; Unit: (K/min) + setpoint: A[ + SignalR[float], ParameterType() + ] # current setpoint during ramping else target; Unit: (K) + mode: A[SignalRW[Cryostat_Mode_Enum], ParameterType()] # mode of regulation + maxpower: A[SignalRW[float], ParameterType()] # Maximum heater power; Unit: (W) + heater: A[SignalR[float], ParameterType()] # current heater setting; Unit: (%) + heaterpower: A[ + SignalR[float], ParameterType() + ] # current heater power; Unit: (W) + pid: A[SignalRW[ndarray], ParameterType()] # regulation coefficients + p: A[ + SignalRW[float], ParameterType() + ] # regulation coefficient 'p'; Unit: (%/K) + i: A[SignalRW[float], ParameterType()] # regulation coefficient 'i' + d: A[SignalRW[float], ParameterType()] # regulation coefficient 'd' + tolerance: A[ + SignalRW[float], ParameterType() + ] # temperature range for stability checking; Unit: (K) + window: A[ + SignalRW[float], ParameterType() + ] # time window for stability checking; Unit: (s) + timeout: A[ + SignalRW[float], ParameterType() + ] # max waiting time for stabilisation check; Unit: (s) + + class Cryo_7_frappy_demo(SECoPNodeDevice): + """short description + + This is a very long description providing all the gory details about the stuff + we are describing. + """ + + # Module Devices + cryo: Cryostat + + # Node Properties + equipment_id: A[SignalR[str], PropertyType()] + firmware: A[SignalR[str], PropertyType()] + description: A[SignalR[str], PropertyType()] + _interfaces: A[SignalR[ndarray], PropertyType()] + + async with init_devices(): + cryo = Cryo_7_frappy_demo( + sec_node_uri="localhost:10769", + ) + + await cryo.cryo.mode.set(Cryostat_Mode_Enum.RAMP) + mode_read = await cryo.cryo.mode.get_value() + + print(mode_read) + assert mode_read == Cryostat_Mode_Enum.RAMP diff --git a/tests/test_classgen.py b/tests/test_classgen.py index f0fae60..0e8d77b 100644 --- a/tests/test_classgen.py +++ b/tests/test_classgen.py @@ -124,11 +124,11 @@ def sample_method(self, value: int) -> str: # Verify code contains expected elements assert "from abc import abstractmethod" in code assert "class TestModule(SECoPDevice):" in code - assert "temperature: A[SignalR[float], ParameterType()]" in code - assert "count: A[SignalRW[int], ParameterType()]" in code + assert "temperature: A[SignalR[float], ParamT()]" in code + assert "count: A[SignalRW[int], ParamT()]" in code assert "class TestNode(SECoPNodeDevice):" in code assert "module1: TestModule" in code - assert "status: A[SignalR[str], PropertyType()]" in code + assert "status: A[SignalR[str], PropT()]" in code assert "def sample_command" in code print("\n✓ All basic tests passed!") @@ -221,13 +221,13 @@ def type2_command(self, mode: str) -> str: type="SignalR", type_param="float", description="this has to be in the final output", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ParameterAttribute( name="setpoint", type="SignalRW", type_param="float", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ], properties=[ @@ -235,13 +235,13 @@ def type2_command(self, mode: str) -> str: name="description", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), PropertyAttribute( name="interface_classes", type="SignalR", type_param="int", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], cmd_plans=[method_type1], @@ -257,13 +257,13 @@ def type2_command(self, mode: str) -> str: name="pressure", type="SignalR", type_param="float", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ParameterAttribute( name="mode", type="SignalRW", type_param="str", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ], properties=[ @@ -271,7 +271,7 @@ def type2_command(self, mode: str) -> str: name="implementation", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], cmd_plans=[method_type2], @@ -291,7 +291,7 @@ def type2_command(self, mode: str) -> str: name="status", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], description="NodeA with Type1 and Type2 modules", @@ -336,13 +336,13 @@ def type3_command(self, count: int) -> int: name="temperature", type="SignalR", type_param="float", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ParameterAttribute( name="setpoint", type="SignalRW", type_param="float", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ], properties=[ @@ -350,13 +350,13 @@ def type3_command(self, count: int) -> int: name="description", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), PropertyAttribute( name="interface_classes", type="SignalR", type_param="list", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], cmd_plans=[method_type1], @@ -373,13 +373,13 @@ def type3_command(self, count: int) -> int: type="SignalRW", type_param="int", description="this is a description", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ParameterAttribute( name="enabled", type="SignalR", type_param="bool", - path_annotation="ParameterType()", + path_annotation=str(ParameterType()), ), ], properties=[ @@ -387,7 +387,7 @@ def type3_command(self, count: int) -> int: name="group", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], cmd_plans=[method_type3], @@ -407,7 +407,7 @@ def type3_command(self, count: int) -> int: name="name", type="SignalR", type_param="str", - path_annotation="PropertyType()", + path_annotation=str(PropertyType()), ), ], description="NodeB with Type1 and Type3 modules", @@ -654,14 +654,14 @@ def test_gen_shall_mass_spec_node( assert "\n# ; Unit: (%)" not in generated_code # Intentionally multiline descriptions should be rendered as multiline comments - assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in generated_code + assert "mid_descriptor: A[SignalRW[ndarray], ParamT()]" in generated_code assert "# Example:" in generated_code assert "# {" in generated_code assert "# mass: [12,15,28,75]," in generated_code assert "# device: [FARADAY,SEM,SEM,SEM]" in generated_code # Long descriptions should be rendered fully below the declaration and wrapped - assert "resolution: A[SignalR[float], ParameterType()]\n" in generated_code + assert "resolution: A[SignalR[float], ParamT()]\n" in generated_code assert ( "# The high mass peak width/valley adjustment used during set up and" in generated_code @@ -675,10 +675,10 @@ def test_gen_shall_mass_spec_node( roundtrip_gen = GenNodeCode(path=str(clean_generated_file)) roundtrip_code = roundtrip_gen.generate_code() - assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in roundtrip_code + assert "mid_descriptor: A[SignalRW[ndarray], ParamT()]" in roundtrip_code assert "Example:" in roundtrip_code assert "\n# ; Unit: (V)" not in roundtrip_code - assert "resolution: A[SignalR[float], ParameterType()]\n" in roundtrip_code + assert "resolution: A[SignalR[float], ParamT()]\n" in roundtrip_code def test_gen_shall_mass_spec_node_no_impl( diff --git a/uv.lock b/uv.lock index cd10a56..1f88b45 100644 --- a/uv.lock +++ b/uv.lock @@ -3005,11 +3005,14 @@ wheels = [ [[package]] name = "wheel" -version = "0.45.1" +version = "0.46.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" }, ] [[package]]