High-level read of enum attribute with AttrQuality.ATTR_INVALID raises exception

It is impossible to set the quality of an enum attribute to ATTR_INVALID.

Setting the quality of an attribute to ATTR_INVALID also sets its value to None. If the attribute has an enum type, pytango will try to convert that None to an enum value, and throw an error.

For example,

@tango.server.attribute(dtype=PowerState)
def power(self, attr):
    attr.set_value(PowerState.ON)
    attr.set_quality(tango.AttrQuality.ATTR_INVALID)

Here, the attr.set_quality(tango.AttrQuality.ATTR_INVALID) overrides the attribute value to None. Pytango then tries to convert that None to a PowerState, resulting in an error:

/usr/local/lib/python3.10/dist-packages/ska_low_mccs_common/device_proxy.py:426: in __getattr__
    return getattr(self._device, name, default_value)
/usr/local/lib/python3.10/dist-packages/tango/device_proxy.py:321: in __DeviceProxy__getattr
    return __get_attribute_value(self, attr_info, name)
/usr/local/lib/python3.10/dist-packages/tango/device_proxy.py:285: in __get_attribute_value
    return enum_class(attr_value)
/usr/lib/python3.10/enum.py:385: in __call__
    return cls.__new__(cls, value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <enum 'powerState'>, value = None

    def __new__(cls, value):
        # all enum instances are actually created during class construction
        # without calling this method; this method is called by the metaclass'
        # __call__ (i.e. Color(3) ), and by pickle
        if type(value) is cls:
            # For lookups like Color(Color.RED)
            return value
        # by-value search for a matching enum member
        # see if it's in the reverse mapping (for hashable values)
        try:
            return cls._value2member_map_[value]
        except KeyError:
            # Not found, no need to do long O(n) search
            pass
        except TypeError:
            # not there, now do long search -- O(n) behavior
            for member in cls._member_map_.values():
                if member._value_ == value:
                    return member
        # still not found -- try _missing_ hook
        try:
            exc = None
            result = cls._missing_(value)
        except Exception as e:
            exc = e
            result = None
        try:
            if isinstance(result, cls):
                return result
            else:
                ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__))
                if result is None and exc is None:
>                   raise ve_exc
E                   ValueError: None is not a valid power

/usr/lib/python3.10/enum.py:710: ValueError

(By the way, I don't think it is a good idea for the attribute value to be nulled by set_quality(INVALID). There are situations when I might want to return a value and yet mark it INVALID: e.g. if my Tango device's connection to its hardware has temporarily dropped out, I might want to return the last known value, marked as INVALID.)