diff --git a/src/fastcs/attributes/attr_r.py b/src/fastcs/attributes/attr_r.py index 3309e2a2a..59a2f227a 100644 --- a/src/fastcs/attributes/attr_r.py +++ b/src/fastcs/attributes/attr_r.py @@ -73,13 +73,13 @@ async def update(self, value: Any) -> None: ValueError: If the value fails to be validated to DType_T """ - self.log_event( - "Attribute set", value=value, value_type=type(value), attribute=self - ) + self.log_event("Attribute set", value=repr(value), attribute=self) _previous_value = self._value self._value = self._datatype.validate(value) + self.log_event("Value validated", value=repr(self._value), attribute=self) + self._on_update_events -= { e for e in self._on_update_events if e.set(self._value) } @@ -94,7 +94,9 @@ async def update(self, value: Any) -> None: await asyncio.gather(*[cb(self._value) for cb in callbacks_to_call]) except Exception as e: logger.opt(exception=e).error( - "On update callbacks failed", attribute=self, value=value + "On update callbacks failed", + attribute=self, + value=repr(self._value), ) raise @@ -201,5 +203,5 @@ def predicate(v: DType_T) -> bool: ) from None self.log_event( - "Value equals target value", target_valuevalue=target_value, attribute=self + "Value equals target value", target_value=target_value, attribute=self ) diff --git a/src/fastcs/attributes/attr_rw.py b/src/fastcs/attributes/attr_rw.py index af6491a04..deff02783 100644 --- a/src/fastcs/attributes/attr_rw.py +++ b/src/fastcs/attributes/attr_rw.py @@ -41,5 +41,5 @@ async def update(self, value: DType_T): await super().update(value) if not self._setpoint_initialised: - await self._call_sync_setpoint_callbacks(value) + await self._call_sync_setpoint_callbacks(self._value) self._setpoint_initialised = True diff --git a/src/fastcs/transports/epics/ca/ioc.py b/src/fastcs/transports/epics/ca/ioc.py index f6ca6c5af..7df4cd836 100644 --- a/src/fastcs/transports/epics/ca/ioc.py +++ b/src/fastcs/transports/epics/ca/ioc.py @@ -171,7 +171,9 @@ def _create_and_link_read_pv( pv = f"{pv_prefix}:{pv_name}" async def async_record_set(value: DType_T): - tracer.log_event("PV set from attribute", topic=attribute, pv=pv, value=value) + tracer.log_event( + "PV set from attribute", topic=attribute, pv=pv, value=repr(value) + ) record.set(cast_to_epics_type(attribute.datatype, value)) @@ -217,13 +219,13 @@ def _create_and_link_write_pv( pv = f"{pv_prefix}:{pv_name}" async def on_update(value): - logger.info("PV put: {pv} = {value}", pv=pv, value=value) + logger.info("PV put: {pv} = {value}", pv=pv, value=repr(value)) await attribute.put(cast_from_epics_type(attribute.datatype, value)) async def set_setpoint_without_process(value: DType_T): tracer.log_event( - "PV setpoint set from attribute", topic=attribute, pv=pv, value=value + "PV setpoint set from attribute", topic=attribute, pv=pv, value=repr(value) ) record.set(cast_to_epics_type(attribute.datatype, value), process=False) diff --git a/tests/test_attributes.py b/tests/test_attributes.py index 27025f48b..cdd428911 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -50,6 +50,33 @@ def test_attr_r(): assert attr.get() == "" +@pytest.mark.asyncio +async def test_attr_update(mocker: MockerFixture): + attr = AttrRW(Int()) + + await attr.update(42) + assert attr.get() == 42 + + await attr.update("100") # type: ignore + assert attr.get() == 100 + + with pytest.raises(ValueError, match="Failed to cast"): + await attr.update("not_an_int") # type: ignore + + attr = AttrRW(Int()) + sync_setpoint_mock = mocker.AsyncMock() + attr.add_sync_setpoint_callback(sync_setpoint_mock) + + await attr.update("200") # type: ignore + assert attr.get() == 200 + sync_setpoint_mock.assert_called_once_with(200) + + sync_setpoint_mock.reset_mock() + await attr.update(20) + assert attr.get() == 20 + sync_setpoint_mock.assert_not_called() + + @pytest.mark.asyncio async def test_wait_for_predicate(mocker: MockerFixture): attr = AttrR(Int(), initial_value=0)