Decorators on write and read methods for attributes break in 9.4.0
In 9.3 and before, we could add python decorators to the read and write methods associated with an attribute. However, in 9.4.0, doing so results in a runtime error (details below).
Decorating methods is a useful construct, both offered by PyTango itself, and for customisation. In LOFAR we use the following decorators:
-
@DebugIt()
as provided by PyTango, to log entry and exit into the method, -
@fault_on_error()
puts the device into FAULT if the method throws, -
@TimeIt()
, which collects statistics on the duration of each method invocation,
The following code reproduces the problem. If the @decorator()
is removed, the code works just fine:
from tango.server import Device, attribute, AttrWriteType
from tango.test_context import DeviceTestContext
from functools import wraps
def decorator():
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"decorator called for function {func} with arguments {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
return inner
class test_device(Device):
A = attribute(dtype=int, access=AttrWriteType.READ_WRITE)
@decorator()
def read_A(self):
return 123
@decorator()
def write_A(self, value):
print(f"Attribute A is written! value={value}")
with DeviceTestContext(test_device) as proxy:
print(proxy.A) # access read method
proxy.A = 42 # access write method
On PyTango 9.4.0, this results in the following error:
Traceback (most recent call last):
File "/home/mol/git/tango/test_device.py", line 30, in <module>
print(proxy.A)
File "/home/mol/.local/lib/python3.9/site-packages/tango/device_proxy.py", line 422, in __DeviceProxy__getattr
return __get_attribute_value(self, attr_info, name)
File "/home/mol/.local/lib/python3.9/site-packages/tango/device_proxy.py", line 338, in __get_attribute_value
attr_value = self.read_attribute(name).value
File "/home/mol/.local/lib/python3.9/site-packages/tango/green.py", line 195, in greener
return executor.run(fn, args, kwargs, wait=wait, timeout=timeout)
File "/home/mol/.local/lib/python3.9/site-packages/tango/green.py", line 109, in run
return fn(*args, **kwargs)
File "/home/mol/.local/lib/python3.9/site-packages/tango/device_proxy.py", line 531, in __DeviceProxy__read_attribute
return __check_read_attribute(self._read_attribute(value, extract_as))
File "/home/mol/.local/lib/python3.9/site-packages/tango/device_proxy.py", line 159, in __check_read_attribute
raise DevFailed(*dev_attr.get_err_stack())
PyTango.DevFailed: DevFailed[
DevError[
desc = TypeError: read_A() missing 1 required positional argument: 'self'
origin = Traceback (most recent call last):
File "/home/mol/.local/lib/python3.9/site-packages/tango/server.py", line 94, in read_attr
ret = worker.execute(read_method)
File "/home/mol/.local/lib/python3.9/site-packages/tango/green.py", line 98, in execute
return fn(*args, **kwargs)
File "/home/mol/git/tango/test_device.py", line 10, in wrapper
return func(*args, **kwargs)
TypeError: read_A() missing 1 required positional argument: 'self'
reason = PyDs_PythonError
severity = ERR]
Related to #516 (closed).
Edited by Jan David Mol