Skip to content

Commit a673575

Browse files
author
James Souter
committed
Get around having to register ref to AtttributeIO method signatures
1 parent 38c5052 commit a673575

File tree

8 files changed

+58
-43
lines changed

8 files changed

+58
-43
lines changed

src/fastcs/attribute_io.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,10 @@ def __init_subclass__(cls) -> None:
1212
args = get_args(cast(Any, cls).__orig_bases__[0])
1313
cls.ref_type = args[0]
1414

15-
async def update(
16-
self, attr: AttrR[T, AttributeIORefT], ref: AttributeIORefT
17-
) -> None:
15+
async def update(self, attr: AttrR[T, AttributeIORefT]) -> None:
1816
raise NotImplementedError()
1917

20-
async def send(
21-
self, attr: AttrRW[T, AttributeIORefT], ref: AttributeIORefT, value: T
22-
) -> None:
18+
async def send(self, attr: AttrRW[T, AttributeIORefT], value: T) -> None:
2319
raise NotImplementedError()
2420

2521

src/fastcs/attributes.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(
3737
f"Attr type must be one of {ATTRIBUTE_TYPES}, "
3838
"received type {datatype.dtype}"
3939
)
40-
self.io_ref = io_ref
40+
self._io_ref = io_ref
4141
self._datatype: DataType[T] = datatype
4242
self._group = group
4343
self.enabled = True
@@ -47,6 +47,15 @@ def __init__(
4747
# changing the units on an int. This should be implemented in the backend.
4848
self._update_datatype_callbacks: list[Callable[[DataType[T]], None]] = []
4949

50+
@property
51+
def io_ref(self) -> AttributeIORefTD:
52+
if self._io_ref is None:
53+
raise RuntimeError(f"{self} has no AttributeIORef")
54+
return self._io_ref
55+
56+
def has_io_ref(self):
57+
return self._io_ref is not None
58+
5059
@property
5160
def datatype(self) -> DataType[T]:
5261
return self._datatype

src/fastcs/backend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ def _add_attribute_updater_tasks(
120120
):
121121
for attribute in controller_api.attributes.values():
122122
match attribute:
123-
case AttrR(io_ref=AttributeIORef(update_period=update_period)) as attribute:
123+
case (
124+
AttrR(_io_ref=AttributeIORef(update_period=update_period)) as attribute
125+
):
124126
callback = _create_updater_callback(attribute)
125127
if update_period is ONCE:
126128
initial_coros.append(callback)

src/fastcs/controller.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,16 @@ async def attribute_initialise(self) -> None:
5858

5959
def _add_io_callbacks(self):
6060
for attr in self.attributes.values():
61-
io = self._attribute_ref_io_map.get(type(attr.io_ref), None)
61+
ref = attr.io_ref if attr.has_io_ref() else None
62+
io = self._attribute_ref_io_map.get(type(ref), None)
6263
if isinstance(attr, AttrW):
6364
# is it on process or write_display?
64-
attr.add_process_callback(self._create_send_callback(io, attr))
65-
if attr.io_ref is None or io is None:
66-
continue
65+
attr.add_process_callback(self._create_send_callback(io, attr, ref))
6766
if isinstance(attr, AttrR):
68-
attr.add_update_callback(self._create_update_callback(io, attr))
67+
attr.add_update_callback(self._create_update_callback(io, attr, ref))
6968

70-
def _create_send_callback(self, io, attr):
71-
if attr.io_ref is None:
69+
def _create_send_callback(self, io, attr, ref):
70+
if ref is None:
7271

7372
async def send_callback(value):
7473
await attr.update_display_without_process(value)
@@ -77,13 +76,13 @@ async def send_callback(value):
7776
else:
7877

7978
async def send_callback(value):
80-
await io.send(attr, attr.io_ref, value)
79+
await io.send(attr, value)
8180
# TODO, should we just then call the above send_callback here?
8281

8382
return send_callback
8483

85-
def _create_update_callback(self, io, attr):
86-
if io is None or attr.io_ref is None:
84+
def _create_update_callback(self, io, attr, ref):
85+
if io is None or ref is None:
8786

8887
async def error_callback():
8988
raise RuntimeError("No AttributeIO registered to handle update")
@@ -92,7 +91,7 @@ async def error_callback():
9291
else:
9392

9493
async def update_callback():
95-
await io.update(attr, attr.io_ref)
94+
await io.update(attr)
9695

9796
return update_callback
9897

@@ -154,7 +153,7 @@ def _validate_io(self):
154153
"""Validate that each Attribute has an AttributeIORef for which the
155154
controller has an associated AttributeIO class."""
156155
for attr in self.attributes.values():
157-
if attr.io_ref is None:
156+
if not attr.has_io_ref():
158157
continue
159158
assert type(attr.io_ref) in self._attribute_ref_io_map, (
160159
f"{self.__class__.__name__} does not have an AttributeIO to handle "

src/fastcs/demo/controllers.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
import enum
55
import json
66
from dataclasses import dataclass
7+
from typing import TypeVar
78

89
from fastcs.attribute_io import AttributeIO
910
from fastcs.attribute_io_ref import AttributeIORef
1011
from fastcs.attributes import AttrR, AttrRW, AttrW
1112
from fastcs.connections import IPConnection, IPConnectionSettings
1213
from fastcs.controller import Controller, SubController
13-
from fastcs.datatypes import Enum, Float, Int, T
14+
from fastcs.datatypes import Enum, Float, Int
1415
from fastcs.wrappers import command, scan
1516

17+
NumberT = TypeVar("NumberT", int, float)
18+
1619

1720
class OnOffEnum(enum.StrEnum):
1821
Off = "0"
@@ -32,23 +35,25 @@ class TemperatureControllerAttributeIORef(AttributeIORef):
3235

3336

3437
class TemperatureControllerAttributeIO(
35-
AttributeIO[TemperatureControllerAttributeIORef, T]
38+
AttributeIO[TemperatureControllerAttributeIORef, NumberT]
3639
):
3740
def __init__(self, connection: IPConnection, suffix: str):
3841
self._connection = connection
3942
self.suffix = suffix
4043

4144
async def send(
42-
self, attr: AttrW, ref: TemperatureControllerAttributeIORef, value: T
45+
self, attr: AttrW[NumberT, TemperatureControllerAttributeIORef], value: NumberT
4346
) -> None:
4447
await self._connection.send_command(
45-
f"{ref.name}{self.suffix}={attr.dtype(value)}\r\n"
48+
f"{attr.io_ref.name}{self.suffix}={attr.dtype(value)}\r\n"
4649
)
4750

4851
async def update(
49-
self, attr: AttrR, ref: TemperatureControllerAttributeIORef
52+
self, attr: AttrR[NumberT, TemperatureControllerAttributeIORef]
5053
) -> None:
51-
response = await self._connection.send_query(f"{ref.name}{self.suffix}?\r\n")
54+
response = await self._connection.send_query(
55+
f"{attr.io_ref.name}{self.suffix}?\r\n"
56+
)
5257
response = response.strip("\r\n")
5358

5459
await attr.set(attr.dtype(response))
@@ -101,7 +106,9 @@ class TemperatureRampController(SubController):
101106
enabled = AttrRW(
102107
Enum(OnOffEnum), io_ref=TemperatureControllerAttributeIORef(name="N")
103108
)
104-
target = AttrR(Float(prec=3), io_ref=TemperatureControllerAttributeIORef(name="T"))
109+
target = AttrR(
110+
Float(prec=3), io_ref=TemperatureControllerAttributeIORef(name="NumberT")
111+
)
105112
actual = AttrR(Float(prec=3), io_ref=TemperatureControllerAttributeIORef(name="A"))
106113
voltage = AttrR(Float(prec=3))
107114

tests/assertable_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ class MyTestAttributeIORef(AttributeIORef):
2424

2525

2626
class MyTestAttributeIO(AttributeIO[MyTestAttributeIORef, T]):
27-
async def update(self, attr: AttrR[T], ref: MyTestAttributeIORef):
27+
async def update(self, attr: AttrR[T]):
2828
print(f"update {attr}")
2929

30-
async def send(self, attr: AttrW[T], ref: MyTestAttributeIORef, value: T):
30+
async def send(self, attr: AttrW[T], value: T):
3131
print(f"sending {attr} = {value}")
3232

3333

tests/test_attribute.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
AttrW,
1414
)
1515
from fastcs.controller import Controller
16-
from fastcs.datatypes import Float, Int, String
16+
from fastcs.datatypes import Float, Int, String, T
1717

1818
NumberT = TypeVar("NumberT", int, float)
1919

@@ -70,8 +70,8 @@ class MyAttributeIORef(AttributeIORef):
7070
cool: int
7171

7272
class MyAttributeIO(AttributeIO[MyAttributeIORef, int]):
73-
async def update(self, attr: AttrR, ref: MyAttributeIORef):
74-
print("I am updating", self.ref_type, ref.cool)
73+
async def update(self, attr: AttrR[T, MyAttributeIORef]):
74+
print("I am updating", self.ref_type, attr.io_ref.cool)
7575

7676
class MyController(Controller):
7777
my_attr = AttrR(Int(), io_ref=MyAttributeIORef(cool=5))
@@ -127,30 +127,32 @@ class DemoParameterAttributeIO(AttributeIO[DemoParameterAttributeIORef, NumberT]
127127
async def update(
128128
self,
129129
attr: AttrR[NumberT],
130-
ref: DemoParameterAttributeIORef,
131130
):
132131
# OK, so this doesn't really work when we have min and maxes...
133132
await attr.set(attr.get() + 1)
134133

135134
async def send(
136135
self,
137136
attr: AttrW[NumberT, DemoParameterAttributeIORef],
138-
ref: DemoParameterAttributeIORef,
139137
value: NumberT,
140138
) -> None:
141139
if (
142-
ref.read_only
140+
attr.io_ref.read_only
143141
): # TODO, this isn't necessary as we can not call process on this anyway
144-
raise RuntimeError(f"Could not set read only attribute {ref.name}")
142+
raise RuntimeError(
143+
f"Could not set read only attribute {attr.io_ref.name}"
144+
)
145145

146-
if (io_min := ref.min) is not None and value < io_min:
146+
if (io_min := attr.io_ref.min) is not None and value < io_min:
147147
raise RuntimeError(
148-
f"Could not set {ref.name} to {value}, min is {ref.min}"
148+
f"Could not set {attr.io_ref.name} to {value}, "
149+
"min is {attr.io_ref.min}"
149150
)
150151

151-
if (io_max := ref.max) is not None and value > io_max:
152+
if (io_max := attr.io_ref.max) is not None and value > io_max:
152153
raise RuntimeError(
153-
f"Could not set {ref.name} to {value}, max is {ref.max}"
154+
f"Could not set {attr.io_ref.name} to {value}, "
155+
f"max is {attr.io_ref.max}"
154156
)
155157
# TODO: we should always end send with a update_display_without_process...
156158

tests/test_backend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ class AttributeIORefTimesCalled(AttributeIORef):
102102
_times_called = 0
103103

104104
class AttributeIOTimesCalled(AttributeIO[AttributeIORefTimesCalled, int]):
105-
async def update(self, attr: AttrR[int], ref: AttributeIORefTimesCalled):
106-
ref._times_called += 1
107-
await attr.set(ref._times_called)
105+
async def update(self, attr: AttrR[int, AttributeIORefTimesCalled]):
106+
attr.io_ref._times_called += 1
107+
await attr.set(attr.io_ref._times_called)
108108

109109
class MyController(Controller):
110110
update_once = AttrR(Int(), io_ref=AttributeIORefTimesCalled(update_period=ONCE))

0 commit comments

Comments
 (0)