# 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