File: attribute_proxy.py

package info (click to toggle)
pytango 10.1.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,304 kB
  • sloc: python: 27,795; cpp: 16,150; sql: 252; sh: 152; makefile: 43
file content (414 lines) | stat: -rw-r--r-- 17,670 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# 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.
"""

from tango._tango import (
    DeviceProxy,
    DevFailed,
    Except,
)
from tango._tango import __AttributeProxy as _AttributeProxy, DbDatum, DbData
from tango.utils import parameter_2_dbdata, get_property_from_db
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)
@_trace_client
def get_attribute_proxy(*args, green_mode=None):
    """
    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, green_mode=green_mode)


def __AttributeProxy__get_property(
    self,
    propname: (
        str
        | DbDatum
        | DbData
        | list[str | bytes | DbDatum]
        | dict[str, DbDatum]
        | dict[str, list[str]]
        | dict[str, object]
    ),
    value=None,
) -> dict[str, list[str]]:
    """

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

    :param propname: Can be one of the following: \n
                    1. :py:obj:`str` [in] - Single property data to be fetched. \n
                    2. :py:obj:`~tango.DbDatum` [in] - Single property data to be fetched. \n
                    3. :py:obj:`~tango.DbData` [in] - Several property data to be fetched. \n
                    4. :py:obj:`list`\\[:py:obj:`str` | :py:obj:`bytes`] [in] - Several property data to be fetched. \n
                    5. :py:obj:`list`\\[:py:obj:`tango.DbDatum`] [in] - Several property data to be fetched. \n
                    6. :py:obj:`dict`\\[:py:obj:`str`, :py:obj:`object`] [in] - Keys are property names
                       to be fetched (values are ignored). \n
                    7. :py:obj:`dict`\\[:py:obj:`str`, :obj:`tango.DbDatum`] [in] - Several `DbDatum.name` are
                       property names to be fetched (keys are ignored). \n

    :param value: Optional. For propname overloads with :py:obj:`str` and :py:obj:`list`\\[:py:obj:`str`] will be filed with the property values, if provided.
    :type value: :obj:`tango.DbData`, optional

    :returns: A :obj:`dict` object, which keys are the property names the value
              associated with each key being a sequence of strings being the
              property value.

    :throws:
        :py:obj:`TypeError`: Raised in case of propname has the wrong type. \n
        :py:obj:`tango.NonDbDevice`: Raised in case of a non-database device error. \n
        :py:obj:`tango.ConnectionFailed`: Raised on connection failure with the database. \n
        :py:obj:`tango.CommunicationFailed`: Raised on communication failure with the database. \n
        :py:obj:`tango.DevFailed`: Raised on a device failure from the database device.`

    .. versionadded:: 10.1.0: overloads with :obj:`dict` as propname parameter

    .. versionchanged:: 10.1.0: raises if propname has an invalid type instead of returning None
    """

    return get_property_from_db(self, propname, value)


def __AttributeProxy__put_property(
    self,
    value: (
        str
        | DbDatum
        | DbData
        | list[str | bytes | DbDatum]
        | dict[str, DbDatum]
        | dict[str, list[str]]
        | dict[str, object]
    ),
) -> None:
    """
    Insert or update a list of properties for this attribute.

    :param value: Can be one of the following: \n
                    1. :py:obj:`str` - Single property data to be inserted. \n
                    2. :py:obj:`~tango.DbDatum` - Single property data to be inserted. \n
                    3. :py:obj:`~tango.DbData` - Several property data to be inserted. \n
                    4. :py:obj:`list`\\[:py:obj:`str` | :py:obj:`bytes` | :py:obj:`~tango.DbDatum`] - Several property data to be inserted. \n
                    5. :py:obj:`dict`\\[:py:obj:`str`, :py:obj:`~tango.DbDatum`] -
                        DbDatum is property to be inserted (keys are ignored). \n
                    6. :py:obj:`dict`\\[:py:obj:`str`, :py:obj:`list`\\[:py:obj:`str`]] - Keys are property names,
                        and value has data to be inserted. \n
                    7. :py:obj:`dict`\\[:py:obj:`str`, :py:obj:`object`] - Keys are property names, and `str(obj)` is property value.

    :throws:
        :py:obj:`TypeError`: Raised in case of value has the wrong type. \n
        :py:obj:`tango.NonDbDevice`: Raised in case of a non-database device error. \n
        :py:obj:`tango.ConnectionFailed`: Raised on connection failure with the database. \n
        :py:obj:`tango.CommunicationFailed`: Raised on communication failure with the database. \n
        :py:obj:`tango.DevFailed`: Raised on a device failure from the database device.`
    """
    value = parameter_2_dbdata(value, "value")
    return self._put_property(value)


def __AttributeProxy__delete_property(
    self,
    value: (
        str
        | DbDatum
        | DbData
        | list[str | bytes | DbDatum]
        | dict[str, DbDatum]
        | dict[str, list[str]]
        | dict[str, object]
    ),
) -> None:
    """
    Delete a the given of properties for this attribute.
    :param value: Can be one of the following: \n
                    1. :py:obj:`str` [in] - Single property data to be deleted. \n
                    2. :py:obj:`~tango.DbDatum` [in] - Single property data to be deleted. \n
                    3. :py:obj:`~tango.DbData` [in] - Several property data to be deleted. \n
                    4. :py:obj:`list`\\[:py:obj:`str` | :py:obj:`bytes` | :py:obj:`~tango.DbDatum`] [in] - Several property data to be deleted. \n
                    5. :py:obj:`dict`\\[:py:obj:`str`, :py:obj:`object`] [in] - Keys are property names
                       to be deleted (values are ignored). \n
                    6. :py:obj:`dict`\\[:py:obj:`str`, :obj:`tango.DbDatum`] [in] - Several `DbDatum.name` are
                       property names to be deleted (keys are ignored). \n

    :throws:
        :py:obj:`TypeError`: Raised in case of value has the wrong type. \n
        :py:obj:`tango.NonDbDevice`: Raised in case of a non-database device error. \n
        :py:obj:`tango.ConnectionFailed`: Raised on connection failure with the database. \n
        :py:obj:`tango.CommunicationFailed`: Raised on communication failure with the database. \n
        :py:obj:`tango.DevFailed`: Raised on a device failure from the database device.`
    """

    new_value = parameter_2_dbdata(value, "value")
    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, green_mode=None):
        self.__initialized = False
        # 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)
        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)
                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.__dict__["_green_mode"] = (
            green_mode if green_mode is not None else get_green_mode()
        )
        self.__initialized = True

    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
        """
        if self.__initialized:
            name = self.__attr_proxy.name()
        else:
            name = "<Unknown: object was not fully initialized>"
        return 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 attribute_proxy_init(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
    )