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)

The first argument to these functions will be a reference to the device instance.

For example, using static attributes and commands:

from tango import AttrWriteType, AttReqType
from tango.server import Device, command, attribute

global_data = {"example_attr1": 100}


def read_example_attr1(device):
    print(f"read from device {device.get_name()}")
    return global_data["example_attr1"]


def write_example_attr1(device, value):
    print(f"write to device {device.get_name()}")
    global_data["example_attr1"] = value


def is_example_attr1_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_cmd1_allowed(device):
    print(f"is_allowed cmd for device {device.get_name()}")
    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

Another example, using dynamic attributes and commands:

from tango import AttrWriteType, AttReqType
from tango.server import Device, command, attribute

global_data = {"example_attr2": 200}


def read_example_attr2(device, attr):
    print(f"read from device {device.get_name()} attr {attr.get_name()}")
    return global_data["example_attr2"]


def write_example_attr2(device, attr):
    print(f"write to device {device.get_name()} attr {attr.get_name()}")
    value = attr.get_write_value()
    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):
    def initialize_dynamic_attributes(self):
        attr = attribute(
            name="example_attr2",
            dtype=int,
            access=AttrWriteType.READ_WRITE,
            fget=read_example_attr2,
            fset=write_example_attr2,
            fisallowed=is_example_attr2_allowed,
        )
        self.add_attribute(attr)
        cmd = command(
            f=self.identity2,
            dtype_in=int,
            dtype_out=int,
            fisallowed=is_cmd2_allowed,
        )
        self.add_command(cmd)

    def identity2(self, value):
        return value