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