Non-bound user functions for read/write/isallowed and commands¶
When providing the user functions that are executed on attribute
and command access, the general requirement was that they
had to be methods on the tango.server.Device
class.
This has led to some confusion when developers try a plain function
instead. There were some ways to work around this, e.g., by patching the
Python device instance __dict__
. From Pytango 9.4.x this is
no longer necessary. User functions can now be defined outside of the
device class, if desired.
This feature applies to both static and dynamic attributes/commands:
attribute read method (
fget
/fread
kwarg)attribute write method (
fset
/fwrite
kwarg)attribute is allowed method (
fisallowed
kwarg)command is allowed method (
fisallowed
kwarg)
Trivial example:
from tango import AttrWriteType, AttReqType
from tango.server import Device, command, attribute
global_data = {"example_attr1": 100}
def read_example_attr1():
return global_data["example_attr1"]
def write_example_attr1(value):
global_data["example_attr1"] = value
def is_example_attr1_allowed(req_type):
assert req_type in (AttReqType.READ_REQ, AttReqType.WRITE_REQ)
return True
def is_cmd1_allowed():
return True
class Test(Device):
example_attr1 = attribute(
fget=read_example_attr1,
fset=write_example_attr1,
fisallowed=is_example_attr1_allowed,
dtype=int,
access=AttrWriteType.READ_WRITE
)
@command(dtype_in=int, dtype_out=int, fisallowed=is_cmd1_allowed)
def identity1(self, value):
return value
As can be seen from the user functions above, they do not get any context about the request. They do not know which device it came from. This can definitely cause problems if a device server hosts multiple instances of the same device.
It is also possible to include another argument in these functions. If present, the first argument will be a reference to the device instance.
For example:
from tango import AttrWriteType, AttReqType
from tango.server import Device, command, attribute
global_data = {"example_attr2": 200}
def read_example_attr2(device):
print(f"read from device {device.get_name()}")
return global_data["example_attr2"]
def write_example_attr2(device, value):
print(f"write to device {device.get_name()}")
global_data["example_attr2"] = value
def is_example_attr2_allowed(device, req_type):
print(f"is_allowed attr for device {device.get_name()}")
assert req_type in (AttReqType.READ_REQ, AttReqType.WRITE_REQ)
return True
def is_cmd2_allowed(device):
print(f"is_allowed cmd for device {device.get_name()}")
return True
class Test(Device):
example_attr2 = attribute(
fget=read_example_attr2,
fset=write_example_attr2,
fisallowed=is_example_attr2_allowed,
dtype=int,
access=AttrWriteType.READ_WRITE
)
@command(dtype_in=int, dtype_out=int, fisallowed=is_cmd2_allowed)
def identity2(self, value):
return value