File: attribute_proxy.py

package info (click to toggle)
pytango 10.0.2-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 10,216 kB
  • sloc: python: 28,206; cpp: 16,380; sql: 255; sh: 82; makefile: 43
file content (484 lines) | stat: -rw-r--r-- 19,540 bytes parent folder | download | duplicates (3)
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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# 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)