# Source code for tango.attribute_proxy

    # SPDX-FileCopyrightText: All Contributors to the PyTango project
    # SPDX-License-Identifier: LGPL-3.0-or-later
    """
    This is an internal PyTango module. It completes the binding of
    :class:`tango.AttributeProxy`.

    To access these members use directly :mod:`tango` module and NOT
    tango.attribute_proxy.
    """

    import collections.abc

    from tango._tango import (
        StdStringVector,
        DbData,
        DbDatum,
        DeviceProxy,
        DevFailed,
        Except,
    )
    from tango._tango import __AttributeProxy as _AttributeProxy
    from tango.utils import seq_2_StdStringVector, seq_2_DbData, DbData_2_dict
    from tango.utils import is_pure_str, is_non_str_seq
    from tango.utils import _get_device_fqtrl_if_necessary
    from tango.green import green, get_green_mode
    from tango.utils import _trace_client
    from tango.device_proxy import __init_device_proxy_internals as init_device_proxy

    __all__ = ("AttributeProxy", "attribute_proxy_init", "get_attribute_proxy")

    @green(consume_green_mode=False)
    def get_attribute_proxy(*args, **kwargs):
        """
        get_attribute_proxy(self, full_attr_name, green_mode=None, wait=True, timeout=True) -> AttributeProxy
        get_attribute_proxy(self, device_proxy, attr_name, green_mode=None, wait=True, timeout=True) -> AttributeProxy

        Returns a new :class:`~tango.AttributeProxy`.
        There is no difference between using this function and the direct
        :class:`~tango.AttributeProxy` constructor if you use the default kwargs.

        The added value of this function becomes evident when you choose a green_mode
        to be *Futures* or *Gevent*. The AttributeProxy constructor internally makes some
        network calls which makes it *slow*. By using one of the *green modes* as
        green_mode you are allowing other python code to be executed in a cooperative way.

        :param full_attr_name: the full name of the attribute
        :type full_attr_name: str
        :param device_proxy: the :class:`~tango.DeviceProxy`
        :type device_proxy: DeviceProxy
        :param attr_name: attribute name for the given device proxy
        :type attr_name: str
        :param green_mode: determines the mode of execution of the device (including
                          the way it is created). Defaults to the current global
                          green_mode (check :func:`~tango.get_green_mode` and
                          :func:`~tango.set_green_mode`)
        :type green_mode: :obj:`~tango.GreenMode`
        :param wait: whether or not to wait for result. If green_mode
                     Ignored when green_mode is Synchronous (always waits).
        :type wait: bool
        :param timeout: The number of seconds to wait for the result.
                        If None, then there is no limit on the wait time.
                        Ignored when green_mode is Synchronous or wait is False.
        :type timeout: float
        :returns:
            if green_mode is Synchronous or wait is True:
                :class:`~tango.AttributeProxy`
            else if green_mode is Futures:
                :class:`concurrent.futures.Future`
            else if green_mode is Gevent:
                :class:`gevent.event.AsynchResult`
        :throws:
            * a *DevFailed* if green_mode is Synchronous or wait is True
              and there is an error creating the attribute.
            * a *concurrent.futures.TimeoutError* if green_mode is Futures,
              wait is False, timeout is not None and the time to create the attribute
              has expired.
            * a *gevent.timeout.Timeout* if green_mode is Gevent, wait is False,
              timeout is not None and the time to create the attribute has expired.

        New in PyTango 8.1.0
        """
        return AttributeProxy(*args, **kwargs)

    def __AttributeProxy__get_property(self, propname, value=None):
        """
        get_property(self, propname, value) -> DbData

                Get a (list) property(ies) for an attribute.

                This method accepts the following types as propname parameter:
                1. string [in] - single property data to be fetched
                2. sequence<string> [in] - several property data to be fetched
                3. tango.DbDatum [in] - single property data to be fetched
                4. tango.DbData [in,out] - several property data to be fetched.
                5. sequence<DbDatum> - several property data to be feteched

                Note: for cases 3, 4 and 5 the 'value' parameter if given, is IGNORED.

                If value is given it must be a tango.DbData that will be filled with the
                property values

            Parameters :
                - propname : (str) property(ies) name(s)
                - value : (tango.DbData) (optional, default is None meaning that the
                          method will create internally a tango.DbData and return
                          it filled with the property values

            Return     : (DbData) containing the property(ies) value(s). If a
                         tango.DbData is given as parameter, it returns the same
                         object otherwise a new tango.DbData is returned

            Throws     : NonDbDevice, ConnectionFailed (with database),
                         CommunicationFailed (with database),
                         DevFailed from database device
        """

        if is_pure_str(propname) or isinstance(propname, StdStringVector):
            new_value = value
            if new_value is None:
                new_value = DbData()
            self._get_property(propname, new_value)
            return DbData_2_dict(new_value)
        elif isinstance(propname, DbDatum):
            new_value = DbData()
            new_value.append(propname)
            self._get_property(new_value)
            return DbData_2_dict(new_value)
        elif isinstance(propname, collections.abc.Sequence):
            if isinstance(propname, DbData):
                self._get_property(propname)
                return DbData_2_dict(propname)

            if is_pure_str(propname[0]):
                new_propname = StdStringVector()
                for i in propname:
                    new_propname.append(i)
                new_value = value
                if new_value is None:
                    new_value = DbData()
                self._get_property(new_propname, new_value)
                return DbData_2_dict(new_value)
            elif isinstance(propname[0], DbDatum):
                new_value = DbData()
                for i in propname:
                    new_value.append(i)
                self._get_property(new_value)
                return DbData_2_dict(new_value)

    def __AttributeProxy__put_property(self, value):
        """
        put_property(self, value) -> None

                Insert or update a list of properties for this attribute.
                This method accepts the following types as value parameter:
                1. tango.DbDatum - single property data to be inserted
                2. tango.DbData - several property data to be inserted
                3. sequence<DbDatum> - several property data to be inserted
                4. dict<str, DbDatum> - keys are property names and value has data to be inserted
                5. dict<str, seq<str>> - keys are property names and value has data to be inserted
                6. dict<str, obj> - keys are property names and str(obj) is property value

            Parameters :
                - value : can be one of the following:
                    1. tango.DbDatum - single property data to be inserted
                    2. tango.DbData - several property data to be inserted
                    3. sequence<DbDatum> - several property data to be inserted
                    4. dict<str, DbDatum> - keys are property names and value has data to be inserted
                    5. dict<str, seq<str>> - keys are property names and value has data to be inserted
                    6. dict<str, obj> - keys are property names and str(obj) is property value

            Return     : None

            Throws     : ConnectionFailed, CommunicationFailed
                         DevFailed from device (DB_SQLError),
                         TypeError
        """
        if isinstance(value, DbData):
            pass
        elif isinstance(value, DbDatum):
            new_value = DbData()
            new_value.append(value)
            value = new_value
        elif is_non_str_seq(value):
            new_value = seq_2_DbData(value)
        elif isinstance(value, collections.abc.Mapping):
            new_value = DbData()
            for k, v in value.items():
                if isinstance(v, DbDatum):
                    new_value.append(v)
                    continue
                db_datum = DbDatum(k)
                if is_non_str_seq(v):
                    seq_2_StdStringVector(v, db_datum.value_string)
                else:
                    db_datum.value_string.append(str(v))
                new_value.append(db_datum)
            value = new_value
        else:
            raise TypeError(
                "Value must be a tango.DbDatum, tango.DbData, "
                "a sequence<DbDatum> or a dictionary"
            )
        return self._put_property(value)

    def __AttributeProxy__delete_property(self, value):
        """
        delete_property(self, value) -> None

            Delete a the given of properties for this attribute.
            This method accepts the following types as value parameter:

                1. string [in] - single property to be deleted
                2. tango.DbDatum [in] - single property data to be deleted
                3. tango.DbData [in] - several property data to be deleted
                4. sequence<string> [in]- several property data to be deleted
                5. sequence<DbDatum> [in] - several property data to be deleted
                6. dict<str, obj> [in] - keys are property names to be deleted
                   (values are ignored)
                7. dict<str, DbDatum> [in] - several DbDatum.name are property names
                   to be deleted (keys are ignored)

            Parameters :
                - value : can be one of the following:

                    1. string [in] - single property data to be deleted
                    2. tango.DbDatum [in] - single property data to be deleted
                    3. tango.DbData [in] - several property data to be deleted
                    4. sequence<string> [in]- several property data to be deleted
                    5. sequence<DbDatum> [in] - several property data to be deleted
                    6. dict<str, obj> [in] - keys are property names to be deleted
                       (values are ignored)
                    7. dict<str, DbDatum> [in] - several DbDatum.name are property
                       names to be deleted (keys are ignored)

            Return     : None

            Throws     : ConnectionFailed, CommunicationFailed
                         DevFailed from device (DB_SQLError),
                         TypeError
        """
        if (
            isinstance(value, DbData)
            or isinstance(value, StdStringVector)
            or is_pure_str(value)
        ):
            new_value = value
        elif isinstance(value, DbDatum):
            new_value = DbData()
            new_value.append(value)
        elif isinstance(value, collections.abc.Sequence):
            new_value = DbData()
            for e in value:
                if isinstance(e, DbDatum):
                    new_value.append(e)
                else:
                    new_value.append(DbDatum(str(e)))
        elif isinstance(value, collections.abc.Mapping):
            new_value = DbData()
            for k, v in value.items():
                if isinstance(v, DbDatum):
                    new_value.append(v)
                else:
                    new_value.append(DbDatum(k))
        else:
            raise TypeError(
                "Value must be a string, tango.DbDatum, "
                "tango.DbData, a sequence or a dictionary"
            )

        return self._delete_property(new_value)

    # It is easier to reimplement AttributeProxy in python using DeviceProxy than
    # wrapping C++ AttributeProxy. However I still rely in the original
    # AttributeProxy for the constructor (parsing strings if necessary) and some
    # other things. With the _method_* functions defined later it is really easy.
    # One reason to do it this way: get_device_proxy() will always return the
    # same tango.DeviceProxy with this implementation. And then we can trust
    # it's automatic event unsubscription to handle events.

    class AttributeProxy:
        """
        AttributeProxy is the high level Tango object which provides the
        client with an easy-to-use interface to TANGO attributes.

        To create an AttributeProxy, a complete attribute name must be set
        in the object constructor.

        Example:
            att = AttributeProxy("tango/tangotest/1/long_scalar")

        Note: PyTango implementation of AttributeProxy is in part a
        python reimplementation of the AttributeProxy found on the C++ API.
        """

        @_trace_client
        def __init__(self, *args, **kwds):
            green_mode = kwds.pop("green_mode", get_green_mode())
            # If TestContext active, short TRL is replaced with fully-qualified
            # TRL, using test server's connection details.  Otherwise, left as-is.
            attr_name = args[0]
            new_attr_name = _get_device_fqtrl_if_necessary(attr_name)
            new_args = [new_attr_name] + list(args[1:])
            try:
                self.__attr_proxy = _AttributeProxy(*new_args, **kwds)
            except DevFailed as orig_err:
                if new_attr_name != attr_name:
                    # If attribute was not found, it could be an attempt to access a real
                    # device with a short name while running TestContext.  I.e., we need
                    # to use the short name so that the real TANGO_HOST will be tried.
                    try:
                        self.__attr_proxy = _AttributeProxy(*args, **kwds)
                    except DevFailed as retry_exc:
                        Except.re_throw_exception(
                            retry_exc,
                            "PyAPI_AttributeProxyInitFailed",
                            f"Failed to create AttributeProxy "
                            f"(tried {new_attr_name!r} => {orig_err.args[0].reason}, and "
                            f"{attr_name!r} => {retry_exc.args[0].reason})",
                            "AttributeProxy.__init__",
                        )
                else:
                    raise
            # get_device_proxy() returns a different python object each time
            # we don't want a different object, so we save the current one.
            self.__dev_proxy = dp = self.__attr_proxy.get_device_proxy()
            init_device_proxy(dp)
            dp.set_green_mode(green_mode)

        def get_device_proxy(self):
            """
            get_device_proxy(self) -> DeviceProxy

                    A method which returns the device associated to the attribute

                Parameters : None

                Return     : (DeviceProxy)
            """
            return self.__dev_proxy

        def name(self):
            """
            name(self) -> str

                    Returns the attribute name

                Parameters : None
                Return     : (str) with the attribute name
            """
            return self.__attr_proxy.name()

        def __str__(self):
            return f"AttributeProxy({self.name()})"

        def __repr__(self):
            return f"AttributeProxy({self.name()})"

    def _method_dev_and_name(dp_fn_name, doc=True):
        def __new_fn(self, *args, **kwds):
            return getattr(self._AttributeProxy__dev_proxy, dp_fn_name)(
                self.name(), *args, **kwds
            )

        if doc:
            __new_fn.__doc__ = (
                "This method is a simple way to do:\n"
                + "\tself.get_device_proxy()."
                + dp_fn_name
                + "(self.name(), ...)\n\n"
                + "For convenience, here is the documentation of DeviceProxy."
                + dp_fn_name
                + "(...):\n"
                + str(getattr(DeviceProxy, dp_fn_name).__doc__)
            )
        __new_fn.__name__ = dp_fn_name
        __new_fn.__qualname__ = f"AttributeProxy.{dp_fn_name}"
        return __new_fn

    def _method_device(dp_fn_name, doc=True):
        def __new_fn(self, *args, **kwds):
            return getattr(self._AttributeProxy__dev_proxy, dp_fn_name)(*args, **kwds)

        if doc:
            __new_fn.__doc__ = (
                "This method is a simple way to do:\n"
                + "\tself.get_device_proxy()."
                + dp_fn_name
                + "(...)\n\n"
                + "For convenience, here is the documentation of DeviceProxy."
                + dp_fn_name
                + "(...):\n"
                + str(getattr(DeviceProxy, dp_fn_name).__doc__)
            )
        __new_fn.__name__ = dp_fn_name
        __new_fn.__qualname__ = f"AttributeProxy.{dp_fn_name}"
        return __new_fn

    def _method_attribute(dp_fn_name, doc=True):
        def __new_fn(self, *args, **kwds):
            return getattr(self._AttributeProxy__attr_proxy, dp_fn_name)(*args, **kwds)

        if doc:
            __new_fn.__doc__ = getattr(_AttributeProxy, dp_fn_name).__doc__
        __new_fn.__name__ = dp_fn_name
        __new_fn.__qualname__ = f"AttributeProxy.{dp_fn_name}"
        return __new_fn

    def __init_AttributeProxy(doc=True):
        _AttributeProxy.get_property = __AttributeProxy__get_property
        _AttributeProxy.put_property = __AttributeProxy__put_property
        _AttributeProxy.delete_property = __AttributeProxy__delete_property

        # General methods
        # AttributeProxy.name                manually defined
        AttributeProxy.status = _method_device("status", doc=doc)
        AttributeProxy.state = _method_device("state", doc=doc)
        AttributeProxy.ping = _method_device("ping", doc=doc)
        AttributeProxy.get_transparency_reconnection = _method_device(
            "get_transparency_reconnection", doc=doc
        )
        AttributeProxy.set_transparency_reconnection = _method_device(
            "set_transparency_reconnection", doc=doc
        )

        # Property methods
        AttributeProxy.get_property = _trace_client(
            _method_attribute("get_property", doc=doc)
        )
        AttributeProxy.put_property = _trace_client(
            _method_attribute("put_property", doc=doc)
        )
        AttributeProxy.delete_property = _trace_client(
            _method_attribute("delete_property", doc=doc)
        )

        # Attribute methods
        AttributeProxy.get_config = _method_dev_and_name("get_attribute_config", doc=doc)
        AttributeProxy.set_config = _method_device("set_attribute_config", doc=doc)

        AttributeProxy.write = _method_dev_and_name("write_attribute", doc=doc)
        AttributeProxy.read = _method_dev_and_name("read_attribute", doc=doc)
        AttributeProxy.write_read = _method_dev_and_name("write_read_attribute", doc=doc)

        # History methods...
        AttributeProxy.history = _method_dev_and_name("attribute_history", doc=doc)

        # Polling administration methods
        AttributeProxy.poll = _method_dev_and_name("poll_attribute", doc=doc)
        AttributeProxy.get_poll_period = _method_dev_and_name(
            "get_attribute_poll_period", doc=doc
        )
        AttributeProxy.is_polled = _method_dev_and_name("is_attribute_polled", doc=doc)
        AttributeProxy.stop_poll = _method_dev_and_name("stop_poll_attribute", doc=doc)

        # Asynchronous methods
        AttributeProxy.read_asynch = _method_dev_and_name("read_attribute_asynch", doc=doc)
        AttributeProxy.read_reply = _method_device("read_attribute_reply", doc=doc)
        AttributeProxy.write_asynch = _method_dev_and_name(
            "write_attribute_asynch", doc=doc
        )
        AttributeProxy.write_reply = _method_device("write_attribute_reply", doc=doc)

        # Event methods
        AttributeProxy.subscribe_event = _method_dev_and_name("subscribe_event", doc=doc)
        AttributeProxy.unsubscribe_event = _method_device("unsubscribe_event", doc=doc)

        AttributeProxy.get_events = _method_device("get_events", doc=doc)
        AttributeProxy.event_queue_size = _method_device("event_queue_size", doc=doc)
        AttributeProxy.get_last_event_date = _method_device("get_last_event_date", doc=doc)
        AttributeProxy.is_event_queue_empty = _method_device(
            "is_event_queue_empty", doc=doc
        )

    def attribute_proxy_init(doc=True):
        __init_AttributeProxy(doc=doc)