Skip to content

Karathon Middlelayer API Reference

import karabo.middlelayer

The Karabo Hash Dictionary

Convert Karabo hashes to Python dictionaries and back.

Bases: OrderedDict

This is the serialization data structure of Karabo

Every data that gets transferred over the network or saved to file by Karabo is in this format.

It is mostly an extended :class:dict.

The big difference to normal Python containers is the dot-access method. The hash has a built-in knowledge about it containing itself. Thus, one can access sub-hashes by hash['key.subhash'].

The other speciality are attributes. In Python, these can be accessed using a second parameter to the brackets, as in hash['key', 'attribute'].

All attributes at the same time can be accessed by hash['key', ...].

Source code in src/pythonKarabo/karabo/native/data/hash.py
 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
class Hash(OrderedDict):
    """This is the serialization data structure of Karabo

    Every data that gets transferred over the network or saved to file
    by Karabo is in this format.

    It is mostly an extended :class:`dict`.

    The big difference to normal Python containers is the dot-access method.
    The hash has a built-in knowledge about it containing itself. Thus,
    one can access sub-hashes by ``hash['key.subhash']``.

    The other speciality are attributes. In Python, these can be accessed
    using a second parameter to the brackets, as in
    ``hash['key', 'attribute']``.

    All attributes at the same time can be accessed by ``hash['key', ...]``."""

    _hashType = HashType.Hash

    def __init__(self, *args):
        if len(args) == 1:
            if isinstance(args[0], Hash):
                OrderedDict.__init__(self)
                for k, v, a in args[0].iterall():
                    OrderedDict.__setitem__(self, k, HashElement(v, a))
            else:
                OrderedDict.__init__(self, args[0])
        else:
            OrderedDict.__init__(self)
            for k, v in zip(args[::2], args[1::2]):
                self.setElement(k, v, {})

    def _path(self, path, auto=False):
        path = path.split(SEPARATOR)
        s = self
        for p in path[:-1]:
            if auto and p not in s:
                OrderedDict.__setitem__(s, p, HashElement(Hash(), {}))
            s = OrderedDict.__getitem__(s, p).data
        if not isinstance(s, Hash):
            raise KeyError(path)
        return s, path[-1]

    def _get(self, path, auto=False):
        if SEPARATOR not in path:
            # We can use the fast path here for methods like ``__contains__``
            return OrderedDict.__getitem__(self, path)

        return OrderedDict.__getitem__(*self._path(path, auto))

    def __str__(self):
        r = ', '.join(f'{k}{self[k, ...]!r}: {self[k]!r}'
                      for k in self)
        return '<' + r + '>'

    def __repr__(self):
        """Return the printable representation of the `Hash`

        the representation will contain types.
        please note that the types are not accurate if the
        data cannot be serialised."""
        if self.empty():
            return "<>"
        tabular = "  "

        def _is_table(value):
            """Check if the value belong to a table value"""
            if (isinstance(value, list) and len(value)
                    and isinstance(value[0], Hash)):
                return HashList.hashlist_format(value) is HashListFormat.Table
            return False

        def _pretty_generator(h, n=0):
            for key, value, attrs in h.iterall():
                if isinstance(value, Hash):
                    yield tabular * n + f"{key}{attrs!r}\n"
                    yield from _pretty_generator(value, n + 2)
                elif _is_table(value):
                    yield tabular * n + f"{key}{attrs!r}\n"
                    for row in value.__repr__().split("\n"):
                        yield tabular * n + row + "\n"
                else:
                    try:
                        hash_type = get_hash_type_from_data(value).name
                    except (ValueError, TypeError):
                        hash_type = "Unknown"
                    yield (tabular * n + f"{key}{attrs!r}: "
                           f"{value!r} => {hash_type}\n")

        return f"<\n{''.join(_pretty_generator(self))}>"

    def _setelement(self, key, value):
        # NOTE: This is a fast path for __setitem__ to be use by the binary
        # deserializer. It must only be called for values in this hash, never
        # for values in sub-hashes!
        assert '.' not in key, "Can't set values in sub-hashes!"
        OrderedDict.__setitem__(self, key, value)

    def getElement(self, path):
        """This is a direct way of getting `value` and `attrs` of the `Hash`
        in a `HashElement` object.

        :returns: A tuple of value and attributes belonging to `path`, e.g.
                  data, attrs = hash.getElement(key)
        """
        path = str(path)
        if SEPARATOR not in path:
            element = OrderedDict.__getitem__(self, path)
        else:
            element = self._get(path)

        return element.data, element.attrs

    def setElement(self, key, value, attrs):
        """This is a direct way of setting `value` and `attrs` in the `Hash`

        Setting both `value` and `attrs` at the same time can provide a fairly
        big speedup! The attributes `attrs` have to be a dictionary.
        """
        assert isinstance(attrs, dict)
        element = HashElement(value, attrs)
        # Note: The karabo Hash only handles string keys...
        key = str(key)
        if SEPARATOR not in key:
            OrderedDict.__setitem__(self, key, element)
        else:
            sub_hash, path = self._path(key, True)
            OrderedDict.__setitem__(sub_hash, path, element)

    __marker = object()

    def pop(self, item, default=__marker):
        """Pop an item from the Hash. This method only returns the value."""
        if item in self:
            ret = self[item]
            del self[item]
            return ret
        if default is self.__marker:
            raise KeyError(item)
        return default

    def __setitem__(self, item, value):
        if isinstance(item, tuple):
            key, attr = item
            if attr is Ellipsis:
                self._get(key).attrs = value
            else:
                self._get(key).attrs[attr] = value
        else:
            item = str(item)
            if SEPARATOR not in item:
                if item not in self:
                    OrderedDict.__setitem__(self, item, HashElement(value, {}))
                else:
                    attrs = OrderedDict.__getitem__(self, item).attrs
                    OrderedDict.__setitem__(self, item,
                                            HashElement(value, attrs))
            else:
                s, p = self._path(item, True)
                if p in s:
                    attrs = OrderedDict.__getitem__(s, p).attrs
                else:
                    attrs = {}
                OrderedDict.__setitem__(s, p, HashElement(value, attrs))

    def __getitem__(self, item):
        """Get an item from the Hash. Specify either a single `key` or both
        key and attribute with [`key`, `attribute`] to retrieve data.

        To retrieve the full attribute dictionary for a specific key, use
        the `Ellipsis` with [`key`, ...]"""
        if isinstance(item, tuple):
            key, attr = item
            if attr is Ellipsis:
                return self._get(key).attrs
            else:
                return self._get(key).attrs[attr]
        else:
            if SEPARATOR not in item:
                return OrderedDict.__getitem__(self, item).data
            return self._get(item).data

    def __delitem__(self, item):
        """Delete an `item` from the Hash"""
        if isinstance(item, tuple):
            key, attr = item
            del self._get(key).attrs[attr]
        else:
            OrderedDict.__delitem__(*self._path(item))

    def __contains__(self, key):
        """Retrieve if a key is contained in the Hash

        :returns: True or False
        """
        try:
            self._get(key)
            return True
        except KeyError:
            return False

    def iterall(self):
        """ Iterate over key, value and attributes

        This behaves like the :meth:`~dict.items` method of Python
        :class:`dict`, except that it yields not only key and value but
        also the attributes for it.
        """
        # NOTE: Because this only iterates over a single level of the Hash,
        # none of the keys contain '.' and thus OrderedDict.__getitem__ can
        # be called directly for a fairly big speedup
        for k in self:
            elem = OrderedDict.__getitem__(self, k)
            yield k, elem.data, elem.attrs

    def items(self):
        """Iterate over key and values of the Hash container"""
        for k in self:
            yield k, OrderedDict.__getitem__(self, k).data

    def merge(self, other, attribute_policy='merge'):
        """Merge the hash other into this hash.

        If the *attribute_policy* is ``'merge'``, the attributes from the other
        hash are merged with the existing ones, otherwise they are overwritten.
        """
        merge = attribute_policy == "merge"
        for k, v in other.items():
            if isinstance(v, Hash):
                if k not in self or self[k] is None:
                    self[k] = Hash()
                self[k].merge(v, attribute_policy)
            else:
                self[k] = v
            if merge:
                self[k, ...].update(other[k, ...])
            else:
                self[k, ...] = other[k, ...].copy()

    def get(self, item, default=None):
        """The graceful item getter function of the Hash

        :param item: The item key for the element
        :param default: The return value if the item is not available
        """
        try:
            return self[item]
        except KeyError:
            return default

    def set(self, item, value):
        self[item] = value

    def setAttribute(self, item, key, value):
        self[item, key] = value

    def getAttribute(self, item, key):
        return self[item, key]

    def getAttributes(self, item):
        """Retrieve all attributes related to `item`"""
        return self[item, ...]

    def has(self, item):
        """Check if the `item` is contained in the Hash"""
        return item in self

    def getKeys(self, keys=None):
        """
        Retrieve a list of keys from the Hash. If the 'keys' parameter
        is provided, the specified keys are added to the given list.

        :param  keys :A list of keys to be added to the result.
        Example:
            h = Hash('a', 1, 'b', 2)
            assert h.getKeys() == ['a', 'b']
            custom_keys = ['c', 'd']
            assert h.getKeys(keys=custom_keys) == ['c', 'd', 'a', 'b']
        """
        if keys is None:
            return list(self.keys())
        return keys.extend(list(self.keys()))

    def hasAttribute(self, item, key):
        """Check if the attribute associated with `item` and `key` is
        contained the Hash"""
        return key in self[item, ...]

    def erase(self, key):
        """Erase an item associated with `key` from the Hash"""
        del self[key]

    def paths(self, *, intermediate=False):
        """Returns all root-to-leaves paths

        :param intermediate: If `True` include all intermediate path from
                             roots to leafs, i.e. ['a.b.c', 'a.b', 'a']
                             instead of ['a.b.c'], defaults to `False`.
        """

        def full_paths(hsh):
            ret = []
            for k, v in hsh.items():
                if isinstance(v, Hash):
                    ret.extend(k + '.' + kk for kk in full_paths(v))
                ret.append(k)
            return ret

        def leaf_paths(hsh, keys=[]):
            ret = []
            for k, v in hsh.items():
                if isinstance(v, Hash) and v:
                    ret.extend(leaf_paths(v, keys=keys + [k]))
                else:
                    ret.append('.'.join(keys + [k]))
            return ret

        return full_paths(self) if intermediate else leaf_paths(self)

    def empty(self):
        """Check if the hash is empty or not

        :returns: True or False
        """
        return len(self) == 0

    def __deepcopy__(self, memo):
        Cls = type(self)
        ret = Cls.__new__(Cls)
        # let deepcopy know now about us!
        memo[id(self)] = ret
        # Take a list for protection from threaded access
        iterable = list(Hash.flat_iterall(self, empty=True))
        for key, value, attrs in iterable:
            ret.setElement(key, deepcopy(value, memo), deepcopy(attrs, memo))

        return ret

    def deepcopy(self):
        """This method retrieves a deepcopy of the `Hash` element"""
        return deepcopy(self)

    def fullyEqual(self, other):
        """Compare two `Hash` objects and check if they have equal content

        Note: This function does not consider the insertion order of elements
        """
        assert isinstance(other, Hash)

        # Do the fast path check first!
        h_paths = sorted(self.paths(intermediate=True))
        other_paths = sorted(other.paths(intermediate=True))
        if h_paths != other_paths:
            return False

        # Take a list for protection from threaded access
        iterable = list(Hash.flat_iterall(other, empty=True))
        for key, value, attr in iterable:
            # We only have to compare values if we do not have a Hash. This
            # is accounted in the paths. But we must compare attributes!
            if not isinstance(value, Hash):
                h_value = self[key]
                if not is_equal(value, h_value):
                    return False

            h_attr = self[key, ...]
            # We can check the attributes `keys` first!
            h_attr_keys = sorted(h_attr.keys())
            a_keys = sorted(attr.keys())
            if h_attr_keys != a_keys:
                return False
            for a_key, a_value in attr.items():
                if not is_equal(a_value, h_attr[a_key]):
                    return False

        return True

    @staticmethod
    def flat_iterall(hsh, base='', empty=False):
        """Recursively iterate over all parameters in a Hash object such that
        a simple iterator interface is exposed.

        :param empty: If set to `True` this will yield an empty Hash followed
                      by all Hash properties. Default is `False`
        """
        assert isinstance(hsh, Hash)

        base = base + '.' if base else ''
        for key, value, attrs in hsh.iterall():
            subkey = base + key
            if isinstance(value, Hash):
                if empty:
                    # Yield an empty Hash as base value
                    yield subkey, Hash(), attrs
                yield from Hash.flat_iterall(
                    value, base=subkey, empty=empty)
            else:
                yield subkey, value, attrs

__contains__(key)

Retrieve if a key is contained in the Hash

:returns: True or False

Source code in src/pythonKarabo/karabo/native/data/hash.py
246
247
248
249
250
251
252
253
254
255
def __contains__(self, key):
    """Retrieve if a key is contained in the Hash

    :returns: True or False
    """
    try:
        self._get(key)
        return True
    except KeyError:
        return False

__delitem__(item)

Delete an item from the Hash

Source code in src/pythonKarabo/karabo/native/data/hash.py
238
239
240
241
242
243
244
def __delitem__(self, item):
    """Delete an `item` from the Hash"""
    if isinstance(item, tuple):
        key, attr = item
        del self._get(key).attrs[attr]
    else:
        OrderedDict.__delitem__(*self._path(item))

__getitem__(item)

Get an item from the Hash. Specify either a single key or both key and attribute with [key, attribute] to retrieve data.

To retrieve the full attribute dictionary for a specific key, use the Ellipsis with [key, ...]

Source code in src/pythonKarabo/karabo/native/data/hash.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def __getitem__(self, item):
    """Get an item from the Hash. Specify either a single `key` or both
    key and attribute with [`key`, `attribute`] to retrieve data.

    To retrieve the full attribute dictionary for a specific key, use
    the `Ellipsis` with [`key`, ...]"""
    if isinstance(item, tuple):
        key, attr = item
        if attr is Ellipsis:
            return self._get(key).attrs
        else:
            return self._get(key).attrs[attr]
    else:
        if SEPARATOR not in item:
            return OrderedDict.__getitem__(self, item).data
        return self._get(item).data

__repr__()

Return the printable representation of the Hash

the representation will contain types. please note that the types are not accurate if the data cannot be serialised.

Source code in src/pythonKarabo/karabo/native/data/hash.py
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
def __repr__(self):
    """Return the printable representation of the `Hash`

    the representation will contain types.
    please note that the types are not accurate if the
    data cannot be serialised."""
    if self.empty():
        return "<>"
    tabular = "  "

    def _is_table(value):
        """Check if the value belong to a table value"""
        if (isinstance(value, list) and len(value)
                and isinstance(value[0], Hash)):
            return HashList.hashlist_format(value) is HashListFormat.Table
        return False

    def _pretty_generator(h, n=0):
        for key, value, attrs in h.iterall():
            if isinstance(value, Hash):
                yield tabular * n + f"{key}{attrs!r}\n"
                yield from _pretty_generator(value, n + 2)
            elif _is_table(value):
                yield tabular * n + f"{key}{attrs!r}\n"
                for row in value.__repr__().split("\n"):
                    yield tabular * n + row + "\n"
            else:
                try:
                    hash_type = get_hash_type_from_data(value).name
                except (ValueError, TypeError):
                    hash_type = "Unknown"
                yield (tabular * n + f"{key}{attrs!r}: "
                       f"{value!r} => {hash_type}\n")

    return f"<\n{''.join(_pretty_generator(self))}>"

deepcopy()

This method retrieves a deepcopy of the Hash element

Source code in src/pythonKarabo/karabo/native/data/hash.py
394
395
396
def deepcopy(self):
    """This method retrieves a deepcopy of the `Hash` element"""
    return deepcopy(self)

empty()

Check if the hash is empty or not

:returns: True or False

Source code in src/pythonKarabo/karabo/native/data/hash.py
375
376
377
378
379
380
def empty(self):
    """Check if the hash is empty or not

    :returns: True or False
    """
    return len(self) == 0

erase(key)

Erase an item associated with key from the Hash

Source code in src/pythonKarabo/karabo/native/data/hash.py
344
345
346
def erase(self, key):
    """Erase an item associated with `key` from the Hash"""
    del self[key]

flat_iterall(hsh, base='', empty=False) staticmethod

Recursively iterate over all parameters in a Hash object such that a simple iterator interface is exposed.

:param empty: If set to True this will yield an empty Hash followed by all Hash properties. Default is False

Source code in src/pythonKarabo/karabo/native/data/hash.py
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
@staticmethod
def flat_iterall(hsh, base='', empty=False):
    """Recursively iterate over all parameters in a Hash object such that
    a simple iterator interface is exposed.

    :param empty: If set to `True` this will yield an empty Hash followed
                  by all Hash properties. Default is `False`
    """
    assert isinstance(hsh, Hash)

    base = base + '.' if base else ''
    for key, value, attrs in hsh.iterall():
        subkey = base + key
        if isinstance(value, Hash):
            if empty:
                # Yield an empty Hash as base value
                yield subkey, Hash(), attrs
            yield from Hash.flat_iterall(
                value, base=subkey, empty=empty)
        else:
            yield subkey, value, attrs

fullyEqual(other)

Compare two Hash objects and check if they have equal content

Note: This function does not consider the insertion order of elements

Source code in src/pythonKarabo/karabo/native/data/hash.py
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
def fullyEqual(self, other):
    """Compare two `Hash` objects and check if they have equal content

    Note: This function does not consider the insertion order of elements
    """
    assert isinstance(other, Hash)

    # Do the fast path check first!
    h_paths = sorted(self.paths(intermediate=True))
    other_paths = sorted(other.paths(intermediate=True))
    if h_paths != other_paths:
        return False

    # Take a list for protection from threaded access
    iterable = list(Hash.flat_iterall(other, empty=True))
    for key, value, attr in iterable:
        # We only have to compare values if we do not have a Hash. This
        # is accounted in the paths. But we must compare attributes!
        if not isinstance(value, Hash):
            h_value = self[key]
            if not is_equal(value, h_value):
                return False

        h_attr = self[key, ...]
        # We can check the attributes `keys` first!
        h_attr_keys = sorted(h_attr.keys())
        a_keys = sorted(attr.keys())
        if h_attr_keys != a_keys:
            return False
        for a_key, a_value in attr.items():
            if not is_equal(a_value, h_attr[a_key]):
                return False

    return True

get(item, default=None)

The graceful item getter function of the Hash

:param item: The item key for the element :param default: The return value if the item is not available

Source code in src/pythonKarabo/karabo/native/data/hash.py
295
296
297
298
299
300
301
302
303
304
def get(self, item, default=None):
    """The graceful item getter function of the Hash

    :param item: The item key for the element
    :param default: The return value if the item is not available
    """
    try:
        return self[item]
    except KeyError:
        return default

getAttributes(item)

Retrieve all attributes related to item

Source code in src/pythonKarabo/karabo/native/data/hash.py
315
316
317
def getAttributes(self, item):
    """Retrieve all attributes related to `item`"""
    return self[item, ...]

getElement(path)

This is a direct way of getting value and attrs of the Hash in a HashElement object.

:returns: A tuple of value and attributes belonging to path, e.g. data, attrs = hash.getElement(key)

Source code in src/pythonKarabo/karabo/native/data/hash.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def getElement(self, path):
    """This is a direct way of getting `value` and `attrs` of the `Hash`
    in a `HashElement` object.

    :returns: A tuple of value and attributes belonging to `path`, e.g.
              data, attrs = hash.getElement(key)
    """
    path = str(path)
    if SEPARATOR not in path:
        element = OrderedDict.__getitem__(self, path)
    else:
        element = self._get(path)

    return element.data, element.attrs

getKeys(keys=None)

Retrieve a list of keys from the Hash. If the 'keys' parameter is provided, the specified keys are added to the given list.

:param keys :A list of keys to be added to the result. Example: h = Hash('a', 1, 'b', 2) assert h.getKeys() == ['a', 'b'] custom_keys = ['c', 'd'] assert h.getKeys(keys=custom_keys) == ['c', 'd', 'a', 'b']

Source code in src/pythonKarabo/karabo/native/data/hash.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def getKeys(self, keys=None):
    """
    Retrieve a list of keys from the Hash. If the 'keys' parameter
    is provided, the specified keys are added to the given list.

    :param  keys :A list of keys to be added to the result.
    Example:
        h = Hash('a', 1, 'b', 2)
        assert h.getKeys() == ['a', 'b']
        custom_keys = ['c', 'd']
        assert h.getKeys(keys=custom_keys) == ['c', 'd', 'a', 'b']
    """
    if keys is None:
        return list(self.keys())
    return keys.extend(list(self.keys()))

has(item)

Check if the item is contained in the Hash

Source code in src/pythonKarabo/karabo/native/data/hash.py
319
320
321
def has(self, item):
    """Check if the `item` is contained in the Hash"""
    return item in self

hasAttribute(item, key)

Check if the attribute associated with item and key is contained the Hash

Source code in src/pythonKarabo/karabo/native/data/hash.py
339
340
341
342
def hasAttribute(self, item, key):
    """Check if the attribute associated with `item` and `key` is
    contained the Hash"""
    return key in self[item, ...]

items()

Iterate over key and values of the Hash container

Source code in src/pythonKarabo/karabo/native/data/hash.py
271
272
273
274
def items(self):
    """Iterate over key and values of the Hash container"""
    for k in self:
        yield k, OrderedDict.__getitem__(self, k).data

iterall()

Iterate over key, value and attributes

This behaves like the :meth:~dict.items method of Python :class:dict, except that it yields not only key and value but also the attributes for it.

Source code in src/pythonKarabo/karabo/native/data/hash.py
257
258
259
260
261
262
263
264
265
266
267
268
269
def iterall(self):
    """ Iterate over key, value and attributes

    This behaves like the :meth:`~dict.items` method of Python
    :class:`dict`, except that it yields not only key and value but
    also the attributes for it.
    """
    # NOTE: Because this only iterates over a single level of the Hash,
    # none of the keys contain '.' and thus OrderedDict.__getitem__ can
    # be called directly for a fairly big speedup
    for k in self:
        elem = OrderedDict.__getitem__(self, k)
        yield k, elem.data, elem.attrs

merge(other, attribute_policy='merge')

Merge the hash other into this hash.

If the attribute_policy is 'merge', the attributes from the other hash are merged with the existing ones, otherwise they are overwritten.

Source code in src/pythonKarabo/karabo/native/data/hash.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def merge(self, other, attribute_policy='merge'):
    """Merge the hash other into this hash.

    If the *attribute_policy* is ``'merge'``, the attributes from the other
    hash are merged with the existing ones, otherwise they are overwritten.
    """
    merge = attribute_policy == "merge"
    for k, v in other.items():
        if isinstance(v, Hash):
            if k not in self or self[k] is None:
                self[k] = Hash()
            self[k].merge(v, attribute_policy)
        else:
            self[k] = v
        if merge:
            self[k, ...].update(other[k, ...])
        else:
            self[k, ...] = other[k, ...].copy()

paths(*, intermediate=False)

Returns all root-to-leaves paths

:param intermediate: If True include all intermediate path from roots to leafs, i.e. ['a.b.c', 'a.b', 'a'] instead of ['a.b.c'], defaults to False.

Source code in src/pythonKarabo/karabo/native/data/hash.py
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
def paths(self, *, intermediate=False):
    """Returns all root-to-leaves paths

    :param intermediate: If `True` include all intermediate path from
                         roots to leafs, i.e. ['a.b.c', 'a.b', 'a']
                         instead of ['a.b.c'], defaults to `False`.
    """

    def full_paths(hsh):
        ret = []
        for k, v in hsh.items():
            if isinstance(v, Hash):
                ret.extend(k + '.' + kk for kk in full_paths(v))
            ret.append(k)
        return ret

    def leaf_paths(hsh, keys=[]):
        ret = []
        for k, v in hsh.items():
            if isinstance(v, Hash) and v:
                ret.extend(leaf_paths(v, keys=keys + [k]))
            else:
                ret.append('.'.join(keys + [k]))
        return ret

    return full_paths(self) if intermediate else leaf_paths(self)

pop(item, default=__marker)

Pop an item from the Hash. This method only returns the value.

Source code in src/pythonKarabo/karabo/native/data/hash.py
187
188
189
190
191
192
193
194
195
def pop(self, item, default=__marker):
    """Pop an item from the Hash. This method only returns the value."""
    if item in self:
        ret = self[item]
        del self[item]
        return ret
    if default is self.__marker:
        raise KeyError(item)
    return default

setElement(key, value, attrs)

This is a direct way of setting value and attrs in the Hash

Setting both value and attrs at the same time can provide a fairly big speedup! The attributes attrs have to be a dictionary.

Source code in src/pythonKarabo/karabo/native/data/hash.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def setElement(self, key, value, attrs):
    """This is a direct way of setting `value` and `attrs` in the `Hash`

    Setting both `value` and `attrs` at the same time can provide a fairly
    big speedup! The attributes `attrs` have to be a dictionary.
    """
    assert isinstance(attrs, dict)
    element = HashElement(value, attrs)
    # Note: The karabo Hash only handles string keys...
    key = str(key)
    if SEPARATOR not in key:
        OrderedDict.__setitem__(self, key, element)
    else:
        sub_hash, path = self._path(key, True)
        OrderedDict.__setitem__(sub_hash, path, element)

Convert a nested Hash h into a dict

Convert a Hash h into a dict. If h is nested, the result will be as well.

Note: This is greedy as we lose all possible attributes of the Hash.

Source code in src/pythonKarabo/karabo/native/data/utils.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def hashToDict(h):
    """Convert a nested Hash `h` into a `dict`

    Convert a Hash h into a dict. If h is nested, the result will be as well.

    Note: This is greedy as we lose all possible attributes of the Hash.
    """
    d = dict()
    for k, v in h.items():
        if isinstance(v, Hash):
            d[k] = hashToDict(v)
        elif isinstance(v, (list, tuple)):
            if len(v) > 0 and isinstance(v[0], Hash):
                # Note: This is a VectorHash
                d[k] = [hashToDict(vv) for vv in v]
            else:
                d[k] = v
        else:
            d[k] = v
    return d

Convert a nested dictionary into a Hash

Source code in src/pythonKarabo/karabo/native/data/utils.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def dictToHash(d):
    """Convert a nested dictionary into a `Hash`"""
    h = Hash()
    for k, v in d.items():
        if isinstance(v, dict):
            h[k] = dictToHash(v)
        elif isinstance(v, (list, tuple)):
            if len(v) > 0 and isinstance(v[0], dict):
                # Note: This is a VectorHash
                h[k] = HashList(dictToHash(vv) for vv in v)
            else:
                h[k] = v
        else:
            h[k] = v
    return h

Accessing Remote Devices with Proxies

Core functionality for interacting with devices via proxies.

get a device proxy for the device deviceId

Request a schema of a remote device and create a proxy object which acts as if one would act on the device itself.

Note that the created device will not be connected to changes on the actual device. In order to achieve this, put the returned device in a with statement as such::

with getDevice("someDevice") as device:
    # do something with device

On the command line, you might prefer using :func:connectDevice.

This is a synchronized coroutine.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
def getDevice(deviceId, *, sync=None, initialize=True, timeout=5):
    """get a device proxy for the device deviceId

    Request a schema of a remote device and create a proxy object which
    acts as if one would act on the device itself.

    Note that the created device will not be connected to changes on the
    actual device. In order to achieve this, put the returned device in a
    with statement as such::

        with getDevice("someDevice") as device:
            # do something with device

    On the command line, you might prefer using :func:`connectDevice`.

    This is a synchronized coroutine.
    """
    if sync is None:
        sync = get_event_loop().sync_set

    ret = _getDevice(deviceId, sync=sync, initialize=initialize,
                     timeout=timeout)
    if asyncio.iscoroutine(ret):
        return DeviceFuture(ret)
    else:
        return ret

get and connect a device proxy for the device deviceId

This connects a given device proxy to the real device such that the proxy's parameters are properly updated. If the deviceId is just a string, :func:getDevice is called on that name, as a shortcut especially on the command line.

If the device is needed only within a specific block, it is nicer tu use instead a with statement as described in :func:getDevice.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
@synchronize
async def connectDevice(device, *, autodisconnect=None, timeout=5):
    """get and connect a device proxy for the device *deviceId*

    This connects a given device proxy to the real device such that the
    proxy's parameters are properly updated. If the *deviceId* is just
    a string, :func:`getDevice` is called on that name, as a shortcut
    especially on the command line.

    If the device is needed only within a specific block, it is nicer
    tu use instead a with statement as described in :func:`getDevice`."""
    if isinstance(device, ProxyBase):
        if autodisconnect is not None:
            raise RuntimeError(
                "autodisconnect can only be set at proxy creation time")
    else:
        if autodisconnect is None:
            factory = DeviceClientProxyFactory
        else:
            factory = AutoDisconnectProxyFactory
        device = await _getDevice(device, sync=get_event_loop().sync_set,
                                  initialize=True,
                                  timeout=timeout, factory=factory)
    if autodisconnect is None:
        ret = device.__enter__()
    else:
        device._interval = autodisconnect
        ret = device
    return ret

wait for an update of a device

request new properties and wait until they arrive. This is the only way to receive changes on a device while the device is not connected.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1110
1111
1112
1113
1114
1115
1116
1117
@synchronize
async def updateDevice(device):
    """wait for an update of a device

    request new properties and wait until they arrive. This is the only
    way to receive changes on a device while the device is not connected."""
    await device.update_proxy()
    return device

Check whether a device represented by a proxy is still running

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1154
1155
1156
1157
def isAlive(proxy):
    """Check whether a device represented by a proxy is still running"""
    assert isinstance(proxy, ProxyBase)
    return proxy._alive

Accessing Remote Devices Directly

A low-overhead API for short-lived interactions (no proxy). Note: state inspection still requires proxies for efficiency.

Set properties of a device

device may either be a device proxy as returned by :func:getDevice, or simply the deviceId as a string. Add the properties to be set as keyword arguments, as in::

setWait("someDevice", speed=3, position=5)
setWait("someDevice", "speed", 3, "position", 5)

Note that for proxy devices this method is normally not necessary, you may just write::

someDevice.speed = 3
someDevice.position = 5

the function waits until the device has acknowledged that the values have been set.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
@synchronize
async def setWait(device, *args, **kwargs):
    """Set properties of a device

    device may either be a device proxy as returned by :func:`getDevice`, or
    simply the deviceId as a string. Add the properties to be set as keyword
    arguments, as in::

        setWait("someDevice", speed=3, position=5)
        setWait("someDevice", "speed", 3, "position", 5)

    Note that for proxy devices this method is normally not necessary,
    you may just write::

        someDevice.speed = 3
        someDevice.position = 5

    the function waits until the device has acknowledged that the values
    have been set."""
    if len(args) % 2 > 0:
        raise RuntimeError("Arguments passed need to be pairs!")

    if isinstance(device, ProxyBase):
        await device._update()
        device = device._deviceId

    kwargs.update(zip(args[::2], args[1::2]))
    h = Hash()
    for k, v in kwargs.items():
        if isinstance(v, KaraboValue):
            h[k] = v.value
        else:
            h[k] = v

    await get_instance().call(device, "slotReconfigure", h)

Same as :func:setWait, but don't wait for acknowledgement

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
def setNoWait(device, *args, **kwargs):
    """Same as :func:`setWait`, but don't wait for acknowledgement"""
    if len(args) % 2 > 0:
        raise RuntimeError("Arguments passed need to be pairs!")

    if isinstance(device, ProxyBase):
        device = device._deviceId

    kwargs.update(zip(args[::2], args[1::2]))
    h = Hash()
    for k, v in kwargs.items():
        if isinstance(v, KaraboValue):
            h[k] = v.value
        else:
            h[k] = v

    get_instance()._sigslot.emit("call", {device: ["slotReconfigure"]}, h)

execute a slot and wait until it finishes

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1101
1102
1103
1104
1105
1106
1107
@synchronize
async def execute(device, slot):
    """execute a slot and wait until it finishes"""
    if isinstance(device, ProxyBase):
        device = device._deviceId
    assert isinstance(slot, str)
    return (await get_instance().call(device, slot))

execute a slot without waiting for it to be finished

device may be a device proxy returned by getDevice, or simply a string with the device id, as in::

executeNoWait("someDevice", "start")

Normally you would call a slot simply using its proxy devive, as in::

someDevice.start()

but then we wait until the device has finished with the operation. If this is not desired, use executeNoWait.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
def executeNoWait(device, slot):
    """execute a slot without waiting for it to be finished

    device may be a device proxy returned by getDevice, or simply a string
    with the device id, as in::

        executeNoWait("someDevice", "start")

    Normally you would call a slot simply using its proxy devive, as in::

        someDevice.start()

    but then we wait until the device has finished with the operation. If
    this is not desired, use executeNoWait."""
    if isinstance(device, ProxyBase):
        device = device._deviceId
    get_instance()._sigslot.emit("call", {device: [slot]})

Running Devices on Servers

Instantiate or shut down devices remotely, with optional fire‑and‑forget.

Instantiate and configure a device on a running server

:param serverId: The serverId of the server on which the device should be started. :param classId: The classId of the device (the corresponding plugin must already be loaded on the server) :param deviceId: The future name of the device in the Karabo installation (will fail if not unique) :param configuration: the configuration of the device (optional)

The keyword arguments are used to further configure the device.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
@synchronize
async def instantiate(serverId, classId, deviceId="", configuration=None,
                      **kwargs):
    """Instantiate and configure a device on a running server

    :param serverId: The serverId of the server on which the device should be
        started.
    :param classId: The classId of the device (the corresponding plugin must
        already be loaded on the server)
    :param deviceId: The future name of the device in the Karabo installation
        (will fail if not unique)
    :param configuration: the configuration of the device (optional)

    The keyword arguments are used to further configure the device. """
    if configuration is None:
        configuration = Hash()
    configuration.update(kwargs)
    h = Hash("classId", classId, "deviceId", deviceId,
             "configuration", configuration)
    ok, msg = await get_instance().call(serverId, "slotStartDevice", h)
    if not ok:
        raise KaraboError(msg)
    return msg

Instantiate and configure a device on a running server

Non-waiting version of :func:instantiate

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
def instantiateNoWait(serverId, classId, deviceId="", configuration=None,
                      **kwargs):
    """Instantiate and configure a device on a running server

    Non-waiting version of :func:`instantiate`"""
    if configuration is None:
        configuration = Hash()
    configuration.update(kwargs)
    h = Hash("classId", classId, "deviceId", deviceId,
             "configuration", configuration)
    get_instance()._sigslot.emit("call", {serverId: ["slotStartDevice"]}, h)

shut down the given device

:param deviceId: may be a device proxy, or just the id of a device

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1006
1007
1008
1009
1010
1011
1012
1013
1014
@synchronize
async def shutdown(device):
    """shut down the given device

    :param deviceId: may be a device proxy, or just the id of a device"""
    if isinstance(device, ProxyBase):
        device = device._deviceId
    ok = await get_instance().call(device, "slotKillDevice")
    return ok

shut down the given device

not waiting version of :func:shutdown

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
1017
1018
1019
1020
1021
1022
1023
def shutdownNoWait(device):
    """shut down the given device

    not waiting version of :func:`shutdown`"""
    if isinstance(device, ProxyBase):
        device = device._deviceId
    get_instance()._sigslot.emit("call", {device: ["slotKillDevice"]})

Inspecting Device Servers

Discover running devices and servers from the command line or within DeviceClientBase.

Return a list of found deviceId's.

This function is a shortcut to find deviceIds with getDevices

:param matchPattern: String pattern, to find the deviceId's containing the matchPattern.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
889
890
891
892
893
894
895
896
897
def findDevices(matchPattern):
    """Return a list of found deviceId's.

    This function is a shortcut to find deviceIds with `getDevices`

    :param matchPattern: String pattern, to find the deviceId's containing
                         the matchPattern.
    """
    return getDevices(matchPattern=matchPattern)

Return a list of found serverId's

This function is a shortcut to find serverId's with getServers

:param matchPattern: String pattern, to find the serverId's containing the matchPattern.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
936
937
938
939
940
941
942
943
944
def findServers(matchPattern):
    """Return a list of found serverId's

    This function is a shortcut to find serverId's with `getServers`

    :param matchPattern: String pattern, to find the serverId's containing
                         the matchPattern.
    """
    return getServers(matchPattern=matchPattern)

Return a list of currently running external clients

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
920
921
922
923
924
def getClients():
    """Return a list of currently running external clients
    """
    instance = get_instance()
    return list(instance.systemTopology["client"])

Return a list of currently running devices

:param serverId: Optional serverId, so that only devices are returned running on device server. :param matchPattern: Optional string pattern, to find deviceId's containing the matchPattern.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
def getDevices(serverId=None, matchPattern=None):
    """Return a list of currently running devices

    :param serverId: Optional serverId, so that only devices are returned
                     running on device server.
    :param matchPattern: Optional string pattern, to find deviceId's containing
                         the matchPattern.
    """
    topology = getTopology()
    if serverId is None:
        ret = list(topology["device"])
    else:
        ret = [k for k, v, a in topology["device"].iterall()
               if a["serverId"] == serverId]
    if matchPattern is not None:
        ret = [dev for dev in ret if matchPattern.lower() in dev.lower()]

    return ret

Return a list of currently running servers

:param matchPattern: Optional string pattern, to find serverId's containing the matchPattern.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
947
948
949
950
951
952
953
954
955
956
957
958
def getServers(matchPattern=None):
    """Return a list of currently running servers

    :param matchPattern: Optional string pattern, to find serverId's containing
                         the matchPattern.
    """
    topology = getTopology()
    ret = list(topology["server"])
    if matchPattern is not None:
        ret = [serv for serv in ret if matchPattern.lower() in serv.lower()]

    return ret

Return a list of device classes (plugins) available on a server

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
961
962
963
964
965
def getClasses(serverId):
    """Return a list of device classes (plugins) available on a server"""
    topology = getTopology()
    servers = topology["server"]
    return servers.getAttributes(serverId)["deviceClasses"]

Return the full topology Hash of the DeviceClient

NOTE: We provide a copy here as the topology might change while working with it.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
927
928
929
930
931
932
933
def getTopology():
    """Return the full topology Hash of the DeviceClient

    NOTE: We provide a copy here as the topology might change while working
    with it.
    """
    return deepcopy(get_instance().systemTopology)

Working with Devices

Retrieve current configuration, schema, or instance info.

Get a configuration from a target device

:param device: deviceId or proxy

NOTE: When passing a proxy, the proxy is updated and the configuration if extracted from the proxy. In this case, ChoiceOfNodes and ListOfNodes (mostly) properties will be omitted in the configuration.

:returns: Configuration Hash

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
@synchronize
async def getConfiguration(device):
    """Get a configuration from a target device

    :param device: deviceId or proxy

    NOTE: When passing a proxy, the proxy is updated and the configuration
    if extracted from the proxy. In this case, `ChoiceOfNodes` and
    `ListOfNodes` (mostly) properties will be omitted in the configuration.

    :returns: Configuration Hash
    """
    if isinstance(device, ProxyBase):
        await device._update()
        return device.configurationAsHash()

    hsh, _ = await get_instance().call(device, "slotGetConfiguration")
    return hsh

Get a schema from a target device

:param device: deviceId or proxy :param onlyCurrentState: Boolean for the state dependent schema The default is False.

:returns: Full Schema object

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@synchronize
async def getSchema(device, onlyCurrentState=False):
    """Get a schema from a target device

    :param device: deviceId or proxy
    :param onlyCurrentState: Boolean for the state dependent schema
                             The default is `False`.

    :returns: Full Schema object
    """
    if isinstance(device, ProxyBase):
        if not onlyCurrentState:
            return Schema(name=device.classId, hash=device._schema_hash)
        else:
            device = device._deviceId

    schema, _ = await get_instance().call(device, "slotGetSchema",
                                          onlyCurrentState)
    return schema

Get the instanceInfo Hash of a device

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
60
61
62
63
64
65
66
@synchronize
async def getInstanceInfo(device):
    """ Get the `instanceInfo` Hash of a device"""
    if isinstance(device, ProxyBase):
        device = device._deviceId

    return await get_instance().call(device, "slotPing", 1)

Configurations and Timestamps

Historic Configuration

Retrieve past configurations or schemas by timestamp.

Get the configuration of a deviceId or proxy at a given time::

getConfigurationFromPast(device, "12:30")

:returns: A karabo configuration hash of the device at the specified time.

The date of the time point is parsed using :func:dateutil.parser.parse, allowing many ways to write the date.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
466
467
468
469
470
471
472
473
474
475
476
477
478
@synchronize
async def getConfigurationFromPast(device, timepoint):
    """Get the configuration of a deviceId or proxy at a given time::

        getConfigurationFromPast(device, "12:30")

    :returns: A karabo configuration hash of the device at the specified time.

    The date of the time point is parsed using :func:`dateutil.parser.parse`,
    allowing many ways to write the date.
    """
    conf, _ = await _get_configuration_from_past(device, timepoint)
    return conf

Get the schema of a deviceId or proxy at a given time::

getSchemaFromPast(deviceId, "12:30")

Returns a karabo schema object of the device at the specified time.

The date of the time point is parsed using :func:dateutil.parser.parse, allowing many ways to write the date.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
481
482
483
484
485
486
487
488
489
490
491
492
493
@synchronize
async def getSchemaFromPast(device, timepoint):
    """Get the schema of a deviceId or proxy at a given time::

        getSchemaFromPast(deviceId, "12:30")

    Returns a karabo schema object of the device at the specified time.

    The date of the time point is parsed using :func:`dateutil.parser.parse`,
    allowing many ways to write the date.
    """
    _, schema = await _get_configuration_from_past(device, timepoint)
    return schema

Named Configurations

Manage configurations by name:

Get the configuration of a deviceId or proxy with a given name::

getConfigurationFromName(device, "run2012")

:returns: A karabo configuration hash of the device saved under the name.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
@synchronize
async def getConfigurationFromName(device, name):
    """Get the configuration of a deviceId or proxy with a given `name`::

        getConfigurationFromName(device, "run2012")

    :returns: A karabo configuration hash of the device saved under the `name`.
    """
    if isinstance(device, ProxyBase):
        device = device._deviceId
    instance = get_instance()
    slot = "slotGetConfigurationFromName"
    h = Hash("deviceId", device, "name", name)
    reply = await instance.call(KARABO_CONFIG_MANAGER, slot, h)
    config = reply["item.config"]

    return config

Get a configuration from a target device

:param device: deviceId or proxy

NOTE: When passing a proxy, the proxy is updated and the configuration if extracted from the proxy. In this case, ChoiceOfNodes and ListOfNodes (mostly) properties will be omitted in the configuration.

:returns: Configuration Hash

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
@synchronize
async def getConfiguration(device):
    """Get a configuration from a target device

    :param device: deviceId or proxy

    NOTE: When passing a proxy, the proxy is updated and the configuration
    if extracted from the proxy. In this case, `ChoiceOfNodes` and
    `ListOfNodes` (mostly) properties will be omitted in the configuration.

    :returns: Configuration Hash
    """
    if isinstance(device, ProxyBase):
        await device._update()
        return device.configurationAsHash()

    hsh, _ = await get_instance().call(device, "slotGetConfiguration")
    return hsh

List the list of configurations of a deviceId with given name_part::

listConfigurationFromName(device, '')

Returns a list of configuration items of the device. Optionally, a name part can be provided to filter the configurations on manager side.

Each configuration item is a Hash containing:

- name: the configuration name
- timepoint: the timepoint the configuration of the device was taken
Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
@synchronize
async def listConfigurationFromName(device, name_part=''):
    """List the list of configurations of a deviceId with given `name_part`::

        listConfigurationFromName(device, '')

    Returns a list of configuration items of the device. Optionally, a `name
    part` can be provided to filter the configurations on manager side.

    Each configuration item is a Hash containing:

        - name: the configuration name
        - timepoint: the timepoint the configuration of the device was taken
    """
    if isinstance(device, ProxyBase):
        device = device._deviceId
    instance = get_instance()
    slot = "slotListConfigurationFromName"
    h = Hash("deviceId", device, "name", name_part)
    reply = await instance.call(KARABO_CONFIG_MANAGER, slot, h)
    configs = reply["items"]

    return configs

Return the list of devices which have a configuration::

listDevicesWithConfiguration()

:returns: List of deviceIds, e.g. ["deviceA", "deviceB"].

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
540
541
542
543
544
545
546
547
548
549
550
551
552
@synchronize
async def listDevicesWithConfiguration():
    """Return the list of devices which have a configuration::

        listDevicesWithConfiguration()

    :returns: List of deviceIds, e.g. ["deviceA", "deviceB"].
    """
    instance = get_instance()
    slot = "slotListDevices"
    reply = await instance.call(KARABO_CONFIG_MANAGER, slot, Hash())
    deviceIds = reply["item"]
    return deviceIds

Instantiate a device from name via the ConfigurationManager::

instantiateFromName(device, name='run2015')
  • device: Mandatory parameter, either deviceId or proxy
  • name: Mandatory parameter, name of configuration
  • classId: Optional parameter for validation of classId
  • serverId: Optional parameter
Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
@synchronize
async def instantiateFromName(
    device: str | ProxyBase, name: str,
        classId: str | None = None, serverId: str | None = None):
    """Instantiate a device from `name` via the ConfigurationManager::

        instantiateFromName(device, name='run2015')

    - device: Mandatory parameter, either deviceId or proxy
    - name: Mandatory parameter, name of configuration
    - classId: Optional parameter for validation of classId
    - serverId: Optional parameter
    """
    if isinstance(device, ProxyBase):
        device = device._deviceId
    h = Hash("deviceId", device, "name", name)
    if classId is not None:
        h["classId"] = classId
    if serverId is not None:
        h["serverId"] = serverId

    instance = get_instance()
    slot = "slotInstantiateDevice"
    reply = await instance.call(KARABO_CONFIG_MANAGER, slot, h)
    success = reply["success"]

    return success

Save configuration(s) in the KaraboConfigurationManager::

  • The parameter devices can be a Karabo proxy, a list of deviceIds, a list of proxies or a mixture of them. It can be as well a single deviceId string.

saveConfigurationFromName(devices, name="proposal2020")

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
@synchronize
async def saveConfigurationFromName(devices, name):
    """Save configuration(s) in the KaraboConfigurationManager::

        - The parameter `devices` can be a Karabo `proxy`, a list of deviceIds,
          a list of proxies or a mixture of them. It can be as well a single
          deviceId string.

        saveConfigurationFromName(devices, name="proposal2020")
    """
    if isinstance(devices, list):
        devices = [dev._deviceId if isinstance(dev, ProxyBase)
                   else dev for dev in devices]
        # Prepare uniqueness!
        devices = list(set(devices))
    elif isinstance(devices, ProxyBase):
        devices = [devices._deviceId]
    else:
        # A single deviceId as string
        devices = [devices]
    instance = get_instance()
    slot = "slotSaveConfigurationFromName"
    h = Hash("deviceIds", devices, "name", name)
    await instance.call(KARABO_CONFIG_MANAGER, slot, h)

Comparing Configurations

Utilities for comparing device configurations:

Compare device configuration (key, values) between present and past

The changes are provided in a list for comparison::

changes = [PRESENT | PAST]

>>> h = compareDeviceWithPast(device, minutesAgo(2))
>>> h
<disableEpsilonFeedback{}: [True, False]>

:param timepoint: The timepoint to compare

:returns: changes Hash

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
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
@synchronize
async def compareDeviceWithPast(device, timepoint):
    """Compare device configuration (key, values) between present and past

    The changes are provided in a list for comparison::

        changes = [PRESENT | PAST]

        >>> h = compareDeviceWithPast(device, minutesAgo(2))
        >>> h
        <disableEpsilonFeedback{}: [True, False]>

    :param timepoint: The timepoint to compare

    :returns: changes Hash
    """
    # Get the config and schema first!
    a_conf, a_schema = await gather(
        getConfiguration(device), getSchema(device))
    b_conf, b_schema = await _get_configuration_from_past(
        device, timepoint)
    # Take into account only reconfigurable and init parameters!
    a_san = sanitize_init_configuration(a_schema, a_conf)
    b_san = sanitize_init_configuration(b_schema, b_conf)
    changes = config_changes(a_san, b_san)

    return changes

Compare device configurations (key, values) of two devices

The changes are provided in a list for comparison::

>>> h = compareDeviceConfiguration(device_a, device_b)
>>> h
<disableEpsilonFeedback{}: [True, False]>

:returns: changes Hash

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
@synchronize
async def compareDeviceConfiguration(device_a, device_b):
    """Compare device configurations (key, values) of two devices

    The changes are provided in a list for comparison::

        >>> h = compareDeviceConfiguration(device_a, device_b)
        >>> h
        <disableEpsilonFeedback{}: [True, False]>

    :returns: changes Hash
    """
    a_schema, b_schema = await gather(
        getSchema(device_a), getSchema(device_b))
    a_conf, b_conf = await gather(
        getConfiguration(device_a), getConfiguration(device_b))

    # Take into account only reconfigurable and init parameters!
    a_san = sanitize_init_configuration(a_schema, a_conf)
    b_san = sanitize_init_configuration(b_schema, b_conf)
    changes = config_changes(a_san, b_san)

    return changes

Return a timestamp from n minutes ago

:param n: minutes, defaults to 1

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
158
159
160
161
162
163
def minutesAgo(n=1):
    """Return a timestamp from `n` minutes ago

    :param n: minutes, defaults to `1`
    """
    return Timestamp(Timestamp() - (MINUTE_IN_SECONDS * n))

Return a timestamp from n hours ago

:param n: hours, defaults to 1

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
166
167
168
169
170
171
def hoursAgo(n=1):
    """Return a timestamp from `n` hours ago

    :param n: hours, defaults to `1`
    """
    return Timestamp(Timestamp() - (HOUR_IN_SECONDS * n))

Return a timestamp from n days ago

:param n: days, defaults to 1

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
174
175
176
177
178
179
def daysAgo(n=1):
    """Return a timestamp from `n` days ago

    :param n: days, defaults to `1`
    """
    return Timestamp(Timestamp() - (DAY_IN_SECONDS * n))

Utility Functions

Convenience helpers:

Return the property value from a proxy or device

:param device: The device instance or proxy object :param path: The full path of the property as string

This function is similar to python's builtin getattr and used with::

prop = get_property(proxy, 'node.subnode.property')

which is equivalent to::

prop = proxy.node.subnode.property
Source code in src/pythonKarabo/karabo/middlelayer/utils.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def get_property(device, path):
    """Return the property value from a proxy or device

    :param device: The device instance or proxy object
    :param path: The full path of the property as string

    This function is similar to python's builtin ``getattr`` and used with::

        prop = get_property(proxy, 'node.subnode.property')

    which is equivalent to::

        prop = proxy.node.subnode.property
    """
    return reduce(lambda obj, key: getattr(obj, key), path.split('.'), device)

Set a property value on a proxy or device

This function has been added in Karabo >= 2.14.

:param device: The device instance or proxy object :param path: The full path of the property as string :param value: The value to be set

This function is similar to python's builtin setattr and used with::

set_property(proxy, 'node.subnode.property', 5)

which is equivalent to::

proxy.node.subnode.property = 5
Source code in src/pythonKarabo/karabo/middlelayer/utils.py
 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
def set_property(device, path, value):
    """Set a property value on a proxy or device

    This function has been added in Karabo >= 2.14.

    :param device: The device instance or proxy object
    :param path: The full path of the property as string
    :param value: The value to be set

    This function is similar to python's builtin ``setattr`` and used with::

        set_property(proxy, 'node.subnode.property', 5)

    which is equivalent to::

        proxy.node.subnode.property = 5
    """
    obj = device
    paths = path.split(".")
    for name in paths[:-1]:
        if not hasattr(obj, name):
            raise AttributeError(
                f"Property {path} is not available on device.")
        obj = getattr(obj, name)
    if not hasattr(obj, paths[-1]):
        raise AttributeError(f"Property {path} is not available on device.")
    setattr(obj, paths[-1], value)

Method to extract an ndarray from a raw Hash

:param data: A hash containing the data hash :param path: The path of the NDArray. If None (default) the input Hash is taken. :param squeeze: If the array should be squeezed if the latest dimension is 1. Default is True.

:returns: A numpy array containing the extracted data

Source code in src/pythonKarabo/karabo/native/data/utils.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def get_array_data(data, path=None, squeeze=True):
    """Method to extract an ``ndarray`` from a raw Hash

    :param data: A hash containing the data hash
    :param path: The path of the NDArray. If `None` (default) the
                 input Hash is taken.
    :param squeeze: If the array should be squeezed if the
                    latest dimension is 1. Default is `True`.

    :returns: A numpy array containing the extracted data
    """
    text = "Expected a Hash, but got type %s instead!" % type(data)
    assert isinstance(data, Hash), text
    array = _build_ndarray(data, path=path, squeeze=squeeze)

    return array

Method to extract an image from a Hash into a numpy array

This is a common use case when processing pipeline data (receiving from an output channel)

:param data: A hash containing the image data hash :param path: optional path of the image data. If no path is provided, the default checks if the data hash contains paths with data.image or image and get the image data.

:returns: A numpy array containing the extracted pixel data

Source code in src/pythonKarabo/karabo/native/data/utils.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def get_image_data(data, path=None):
    """Method to extract an ``image`` from a Hash into a numpy array

    This is a common use case when processing pipeline data (receiving from an
    output channel)

    :param data: A hash containing the image data hash
    :param path: optional path of the image data. If no path is provided,
                 the default checks if the data hash contains paths with
                 `data.image` or `image` and get the image data.

    :returns: A numpy array containing the extracted pixel data
    """
    text = "Expected a Hash, but got type %s instead!" % type(data)
    assert isinstance(data, Hash), text
    if path is not None:
        return _build_ndarray(data[path], path="pixels")
    elif "data.image" in data:
        return _build_ndarray(data["data.image"], path="pixels")
    elif "image" in data:
        return _build_ndarray(data["image"], path="pixels")

Return the maximum value of the iterable

This function takes into account KaraboValues for newest timestamp generation.

Source code in src/pythonKarabo/karabo/middlelayer/unitutil.py
25
26
27
28
29
30
31
32
33
34
35
36
37
def maximum(iterable):
    """Return the maximum value of the iterable

    This function takes into account KaraboValues for newest timestamp
    generation.
    """
    assert isinstance(iterable, Iterable)

    ret = max(iterable)
    ret = wrap(copy.copy(ret))
    ret.timestamp = newest_timestamp(iterable)

    return ret

Return the minimum value of the iterable

This function takes into account KaraboValues for newest timestamp generation.

Source code in src/pythonKarabo/karabo/middlelayer/unitutil.py
40
41
42
43
44
45
46
47
48
49
50
51
52
def minimum(iterable):
    """Return the minimum value of the iterable

    This function takes into account KaraboValues for newest timestamp
    generation.
    """
    assert isinstance(iterable, Iterable)

    ret = min(iterable)
    ret = wrap(copy.copy(ret))
    ret.timestamp = newest_timestamp(iterable)

    return ret

Decorate a function to remove QuantityValue/KaraboValue input

This function works as well with async declarations and can be used as::

@removeQuantity def calculate(x, y): assert not isinstance(x, KaraboValue) assert not isinstance(y, KaraboValue) return x, y

This decorator does not cast to base units! In case of booleans,

they can be compared by identity.

Source code in src/pythonKarabo/karabo/middlelayer/unitutil.py
 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
def removeQuantity(func):
    """Decorate a function to remove QuantityValue/KaraboValue input

    This function works as well with async declarations and can be used as::

    @removeQuantity
    def calculate(x, y):
        assert not isinstance(x, KaraboValue)
        assert not isinstance(y, KaraboValue)
        return x, y

    Note: This decorator does not cast to base units! In case of booleans,
          they can be compared by identity.
    """

    def _convert_input(args, kwargs):
        n_args = [
            value.value if isinstance(value, KaraboValue) else value
            for value in tuple(args)]
        n_kwargs = {
            key: value.value if isinstance(value, KaraboValue) else value
            for key, value in kwargs.items()}
        return n_args, n_kwargs

    if iscoroutinefunction(func):

        @wraps(func)
        async def wrapper(*args, **kwargs):
            n_args, n_kwargs = _convert_input(args, kwargs)
            return await func(*n_args, **n_kwargs)
    else:
        @wraps(func)
        def wrapper(*args, **kwargs):
            n_args, n_kwargs = _convert_input(args, kwargs)
            return func(*n_args, **n_kwargs)

    return wrapper

A versatile profiling class

Use this class either as a context manager or as decorator::

with profiler():
    # classic context manager

with profiler("Long computation 1"):
    # do something but provide a name for the context

The class can also be used for decoration of functions (async works)

@profiler()
async def do_something()
    # do something

async with profiler("Async with statement"):
    # do something with name in context
Source code in src/pythonKarabo/karabo/middlelayer/utils.py
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
class profiler:
    """A versatile profiling class

    Use this class either as a context manager or as decorator::

        with profiler():
            # classic context manager

        with profiler("Long computation 1"):
            # do something but provide a name for the context

        The class can also be used for decoration of functions (async works)

        @profiler()
        async def do_something()
            # do something

        async with profiler("Async with statement"):
            # do something with name in context

    """

    def __init__(self, name=None):
        self.name = name
        self.t_start = None

    def __enter__(self):
        self.t_start = perf_counter()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        elapsed = perf_counter() - self.t_start
        name = f"{self.name}:" if self.name is not None else ":"
        print(f"With block {name} time elapsed {elapsed}")

    async def __aenter__(self):
        return self.__enter__()

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        return self.__exit__(exc_type, exc_val, exc_tb)

    def __call__(self, func):
        """Decorate a function to profile the execution time"""
        name = func.__name__ if self.name is None else self.name
        if asyncio.iscoroutinefunction(func):
            @wraps(func)
            async def wrapper(*args, **kwargs):
                t_start = perf_counter()
                ret = await func(*args, **kwargs)
                elapsed = perf_counter() - t_start
                print(f"{name} took {elapsed}")
                return ret
        else:
            @wraps(func)
            def wrapper(*args, **kwargs):
                t_start = perf_counter()
                ret = func(*args, **kwargs)
                elapsed = perf_counter() - t_start
                print(f"{name} took {elapsed}")
                return ret

        return wrapper

__call__(func)

Decorate a function to profile the execution time

Source code in src/pythonKarabo/karabo/middlelayer/utils.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def __call__(self, func):
    """Decorate a function to profile the execution time"""
    name = func.__name__ if self.name is None else self.name
    if asyncio.iscoroutinefunction(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            t_start = perf_counter()
            ret = await func(*args, **kwargs)
            elapsed = perf_counter() - t_start
            print(f"{name} took {elapsed}")
            return ret
    else:
        @wraps(func)
        def wrapper(*args, **kwargs):
            t_start = perf_counter()
            ret = func(*args, **kwargs)
            elapsed = perf_counter() - t_start
            print(f"{name} took {elapsed}")
            return ret

    return wrapper

A decorator to be used to validate arguments with their annotations

Usage examples::

@validate_args
def my_func(param1: str, param2: (float, int)):
    pass

The decorator will raise if the function is called with wrong type input.

Note: Booleans are also valid for integers!

Source code in src/pythonKarabo/karabo/common/decorators.py
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
def validate_args(func):
    """A decorator to be used to validate arguments with their annotations

    Usage examples::

        @validate_args
        def my_func(param1: str, param2: (float, int)):
            pass

    The decorator will raise if the function is called with wrong type input.

    Note: Booleans are also valid for integers!
    """

    @wraps(func)
    def wrapper(*args):
        argspec = inspect.getfullargspec(func).args
        for index, name in enumerate(argspec):
            if not isinstance(args[index], func.__annotations__[name]):
                raise ValueError(
                    "Argument {} got wrong input "
                    "type {}".format(name, args[index])
                )
        return func(*args)

    return wrapper

Derive and return an associated descriptor from data

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
def get_descriptor_from_data(data):
    """Derive and return an associated descriptor from `data`"""
    try:
        if data.ndim == 1 and isinstance(data, np.ndarray):
            return NumpyVector.vstrs[data.dtype.str]
        else:
            return Type.strs[data.dtype.str]
    except AttributeError:
        if isinstance(data, Hash):
            return TypeHash
        elif isinstance(data, Schema):
            return TypeSchema
        elif isinstance(data, HashList):
            return VectorHash
        elif isinstance(data, (bool, BoolValue)):
            return Bool
        elif isinstance(data, (Enum, numbers.Integral)):
            return Int32
        elif isinstance(data, numbers.Real):
            return Double
        elif isinstance(data, bytes):
            return VectorChar
        elif isinstance(data, str):
            return String
        elif isinstance(data, list):
            if data:
                return get_descriptor_from_data(data[0]).vectortype
            else:
                return VectorString
        elif not isSet(data):
            return TypeNone
        try:
            memoryview(data)
            return ByteArray
        except TypeError:
            raise TypeError(f'unknown datatype {data.__class__}')

Get a HashType enum corresponding to data

:param data: Any kind of data typically used in the control network :returns: HashType Enum

Source code in src/pythonKarabo/karabo/native/data/hash.py
572
573
574
575
576
577
578
def get_hash_type_from_data(data):
    """Get a `HashType` enum corresponding to `data`

    :param data: Any kind of data typically used in the control network
    :returns: HashType Enum
    """
    return HashType(_get_hash_num_from_data(data))

A compare function deals with element-wise comparison

Source code in src/pythonKarabo/karabo/native/data/hash.py
634
635
636
637
638
639
640
641
642
643
644
645
def is_equal(a, b):
    """A compare function deals with element-wise comparison"""
    try:
        res = (a == b)
    except ValueError:
        # If shapes can't be broadcasted together, we have a dimension
        # mismatch of ndarrays
        array_check = map(lambda x: isinstance(x, np.ndarray), (a, b))
        assert any(array_check)
        return False

    return all(res) if isinstance(res, Iterable) else res

Compare old/new values to determine if there is a real difference

This function has been added in Karabo >= 2.14.

Source code in src/pythonKarabo/karabo/native/data/compare.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def has_changes(old_value, new_value):
    """Compare old/new values to determine if there is a real difference

    This function has been added in Karabo >= 2.14.
    """
    try:
        if old_value is None:
            changes = new_value is not None
        elif _is_nonintegral_number(old_value):
            changes = _has_floating_changes(old_value, new_value)
        elif isinstance(old_value, np.ndarray):
            changes = _has_array_changes(old_value, new_value)
        elif isinstance(old_value, list):
            changes = _has_list_changes(old_value, new_value)
        else:
            changes = str(old_value) != str(new_value)
    except TypeError:
        # When old_value and new_value have different types and cannot be
        # operated/compared together, it signifies that there are changes.
        changes = True

    return changes

Deprecated function to deepcopy simple data structures

Source code in src/pythonKarabo/karabo/native/data/hash.py
648
649
650
651
652
653
654
655
656
657
def simple_deepcopy(value: Any):
    """Deprecated function to deepcopy simple data structures"""
    import warnings
    warnings.warn(
        "simple_deepcopy() is deprecated and will be removed in a "
        "future version. Use copy.deepcopy() directly instead.",
        DeprecationWarning,
        stacklevel=2
    )
    return deepcopy(value)

Writing a Device

Base classes and exceptions for device authors:

The base class for everything that has Karabo attributes.

A class with Karabo attributes inherits from this class. The attributes are then defined as follows::

from karabo.native import Configurable, Int32, String

class Spam(Configurable):
    ham = Int32()
    eggs = String()
Source code in src/pythonKarabo/karabo/native/schema/configurable.py
 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
class Configurable:
    """The base class for everything that has Karabo attributes.

    A class with Karabo attributes inherits from this class.
    The attributes are then defined as follows::

        from karabo.native import Configurable, Int32, String

        class Spam(Configurable):
            ham = Int32()
            eggs = String()
    """
    _subclasses = {}
    daqDataType = None
    displayType = None
    classId = None
    schema = None

    def __init__(self, configuration={}):
        """Initialize this object with the configuration

        :param configuration: a :class:`dict` or a
        :class:`~karabo.middlelayer.Hash` which contains the
        initial values to be used for the Karabo attributes.
        Default values of attributes are handled here.

        :param parent: the :class:`Configurable` object this object
        belongs to
        :param key: the name of the attribute used in this object.

        Top-layer objects like devices are always called with one
        parameter (configuration) only, so if you are inheriting from
        this class you only need to have ``parent`` and ``key`` as a
        parameter if your class should be usable as a component in
        another class.
        """
        self._parents = WeakKeyDictionary()
        self._initializers = []
        for k in self._allattrs:
            t = getattr(type(self), k)
            init = []
            if k in configuration:
                v = configuration[k]
                init = t.checkedInit(self, v)
                del configuration[k]
            else:
                init = t.checkedInit(self)
            self._initializers.extend(init)

    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        for k, v in cls.__dict__.items():
            if isinstance(v, Overwrite):
                setattr(cls, k, v.overwrite(getattr(super(cls, cls), k)))
        cls._attrs = [k for k in cls.__dict__
                      if isinstance(getattr(cls, k), Descriptor)]
        allattrs = []
        seen = set()
        for base in cls.__mro__[::-1]:
            for attr in getattr(base, "_attrs", []):
                if attr not in seen:
                    allattrs.append(attr)
                    seen.add(attr)
        cls._allattrs = allattrs
        cls._subclasses = {}
        for b in cls.__bases__:
            if issubclass(b, Configurable):
                b._subclasses[cls.__name__] = cls

    @classmethod
    def getClassSchema(cls, device=None, state=None):
        schema = Schema(cls.__name__)
        for base in cls.__mro__[::-1]:
            for attr in getattr(base, "_attrs", []):
                descr = getattr(base, attr)
                if (state is None or descr.allowedStates is None or
                        state in descr.allowedStates):
                    sub_schema, attrs = descr.toSchemaAndAttrs(device, state)
                    schema.hash.setElement(attr, sub_schema, attrs)
        return schema

    def get_root(self):
        """Return the root instance of this Configurable"""
        root = self
        while True:
            try:
                root = next(iter(root._parents))
            except StopIteration:
                break

        return root

    def getDeviceSchema(self, state=None):
        return self.getClassSchema(self, state)

    def configurationAsHash(self):
        r = Hash()
        for k in self._allattrs:
            a = getattr(self, k, None)
            if isSet(a):
                v = getattr(type(self), k)
                if isinstance(v, Slot):
                    continue
                value, attrs = v.toDataAndAttrs(a)
                r.setElement(k, value, attrs)
        return r

    def __dir__(self):
        """Return all attributes of this object.

        This is mostly for tab-expansion in IPython."""
        return list(self._allattrs)

    def setValue(self, descriptor, value):
        if isinstance(value, KaraboValue) and value.timestamp is None:
            value.timestamp = get_timestamp()
        elif isinstance(value, KaraboValue) and value.tid == 0:
            value.timestamp = get_timestamp(value.timestamp)
        if isSet(value):
            # calls the setChildValue of the signalslotable
            self.setChildValue(descriptor.key, value, descriptor)
        old = self.__dict__.get(descriptor.key)
        if isinstance(old, Configurable):
            del old._parents[self]
        if isinstance(value, Configurable):
            value._parents[self] = descriptor.key
        self.__dict__[descriptor.key] = value

    def setChildValue(self, key, value, desc):
        """Set all values in Nodes"""
        for parent, parentkey in self._parents.items():
            parent.setChildValue(f"{parentkey}.{key}", value, desc)

    def _getValue(self, key):
        ret = self.__dict__.get(key)
        if ret is None:
            ret = NoneValue(descriptor=getattr(self.__class__, key))
            ret._parent = self
        return ret

    async def slotReconfigure(self, config):
        props = ((getattr(self.__class__, k), v) for k, v in config.items())
        setters = sum((t.checkedSet(self, v) for t, v in props), [])
        setters = (s() for s in setters)
        await gather(*[s for s in setters if s is not None])

    async def set_setter(self, config, only_changes=False):
        """Internal handler to set a Hash on the Configurable via the setter
        functions

        :param only_changes: Boolean to check if only changed values should
                             be set, the default is `False`.
        """
        setters = [partial(desc.setter, instance, value)
                   for (desc, instance, value)
                   in _get_setters(self, config, only_changes)]
        setters = (s() for s in setters)
        await gather(*[s for s in setters if s is not None])

    def set(self, config, only_changes=False, strict=True):
        """Internal handler to set a Hash on the Configurable

        :param only_changes: Boolean to check if only changed values should
                             be set, the default is `False`.
        """
        setters = _get_setters(self, config, only_changes, strict)
        for desc, instance, value in setters:
            # This is similar to descriptor's `__set__`
            value._parent = instance
            instance.setValue(desc, value)

    async def _run(self):
        """ post-initialization of a Configurable

        As __init__ is not a coroutine, it cannot do anything that takes
        time, and actually should not anyways. Everything needed to get
        this configurable running is thus done in this method.

        This method should only be overridden inside Karabo (thus
        the underscore).

        Do not forget to ``await super()._run(**kwargs)``.
        """
        await gather(*self._initializers)
        del self._initializers

    def _use(self):
        """this method is called each time an attribute of this configurable
        is read"""

__dir__()

Return all attributes of this object.

This is mostly for tab-expansion in IPython.

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
171
172
173
174
175
def __dir__(self):
    """Return all attributes of this object.

    This is mostly for tab-expansion in IPython."""
    return list(self._allattrs)

__init__(configuration={})

Initialize this object with the configuration

:param configuration: a :class:dict or a :class:~karabo.middlelayer.Hash which contains the initial values to be used for the Karabo attributes. Default values of attributes are handled here.

:param parent: the :class:Configurable object this object belongs to :param key: the name of the attribute used in this object.

Top-layer objects like devices are always called with one parameter (configuration) only, so if you are inheriting from this class you only need to have parent and key as a parameter if your class should be usable as a component in another class.

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
 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
def __init__(self, configuration={}):
    """Initialize this object with the configuration

    :param configuration: a :class:`dict` or a
    :class:`~karabo.middlelayer.Hash` which contains the
    initial values to be used for the Karabo attributes.
    Default values of attributes are handled here.

    :param parent: the :class:`Configurable` object this object
    belongs to
    :param key: the name of the attribute used in this object.

    Top-layer objects like devices are always called with one
    parameter (configuration) only, so if you are inheriting from
    this class you only need to have ``parent`` and ``key`` as a
    parameter if your class should be usable as a component in
    another class.
    """
    self._parents = WeakKeyDictionary()
    self._initializers = []
    for k in self._allattrs:
        t = getattr(type(self), k)
        init = []
        if k in configuration:
            v = configuration[k]
            init = t.checkedInit(self, v)
            del configuration[k]
        else:
            init = t.checkedInit(self)
        self._initializers.extend(init)

get_root()

Return the root instance of this Configurable

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
145
146
147
148
149
150
151
152
153
154
def get_root(self):
    """Return the root instance of this Configurable"""
    root = self
    while True:
        try:
            root = next(iter(root._parents))
        except StopIteration:
            break

    return root

set(config, only_changes=False, strict=True)

Internal handler to set a Hash on the Configurable

:param only_changes: Boolean to check if only changed values should be set, the default is False.

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
223
224
225
226
227
228
229
230
231
232
233
def set(self, config, only_changes=False, strict=True):
    """Internal handler to set a Hash on the Configurable

    :param only_changes: Boolean to check if only changed values should
                         be set, the default is `False`.
    """
    setters = _get_setters(self, config, only_changes, strict)
    for desc, instance, value in setters:
        # This is similar to descriptor's `__set__`
        value._parent = instance
        instance.setValue(desc, value)

setChildValue(key, value, desc)

Set all values in Nodes

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
192
193
194
195
def setChildValue(self, key, value, desc):
    """Set all values in Nodes"""
    for parent, parentkey in self._parents.items():
        parent.setChildValue(f"{parentkey}.{key}", value, desc)

set_setter(config, only_changes=False) async

Internal handler to set a Hash on the Configurable via the setter functions

:param only_changes: Boolean to check if only changed values should be set, the default is False.

Source code in src/pythonKarabo/karabo/native/schema/configurable.py
210
211
212
213
214
215
216
217
218
219
220
221
async def set_setter(self, config, only_changes=False):
    """Internal handler to set a Hash on the Configurable via the setter
    functions

    :param only_changes: Boolean to check if only changed values should
                         be set, the default is `False`.
    """
    setters = [partial(desc.setter, instance, value)
               for (desc, instance, value)
               in _get_setters(self, config, only_changes)]
    setters = (s() for s in setters)
    await gather(*[s for s in setters if s is not None])

Bases: InjectMixin, SignalSlotable

This is the base class for all devices.

It inherits from :class:~karabo.middlelayer.Configurable and thus you can define expected parameters for it.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
 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
class Device(InjectMixin, SignalSlotable):
    """This is the base class for all devices.

    It inherits from :class:`~karabo.middlelayer.Configurable` and thus
    you can define expected parameters for it.
    """

    # Version e.g. for classVersion - to be overwritten by external classes
    __version__ = karaboVersion

    _serverId_ = String(
        displayedName="_ServerID_",
        description="Do not set this property, it will be set by the "
                    "device-server",
        requiredAccessLevel=AccessLevel.EXPERT,
        assignment=Assignment.INTERNAL, accessMode=AccessMode.INITONLY,
        defaultValue="__none__",
    )

    classId = String(
        displayedName="ClassID",
        description="The (factory)-name of the class of this device",
        accessMode=AccessMode.READONLY,
    )

    classVersion = String(
        displayedName="Class version",
        description="The version of the class of this device",
        requiredAccessLevel=AccessLevel.EXPERT,
        accessMode=AccessMode.READONLY,
        # No version dependent default value: It would make the static
        # schema version dependent, i.e. introduce fake changes.
    )

    karaboVersion = String(
        displayedName="Karabo version",
        description="The version of the Karabo framework running this device",
        requiredAccessLevel=AccessLevel.EXPERT,
        accessMode=AccessMode.READONLY,
        # No version dependent default value, see above at "classVersion".
    )

    serverId = String(
        displayedName="ServerID",
        description="The device-server which this device is running on",
        requiredAccessLevel=AccessLevel.EXPERT,
        accessMode=AccessMode.READONLY,
    )

    hostName = String(
        displayedName="Host",
        description="Do not set this property, it will be set by the "
                    "device-server",
        requiredAccessLevel=AccessLevel.EXPERT,
        assignment=Assignment.INTERNAL, accessMode=AccessMode.INITONLY,
    )

    pid = Int32(
        displayedName="Process ID",
        defaultValue=0,
        description="The unix process ID of the device (i.e. of the server)",
        requiredAccessLevel=AccessLevel.EXPERT,
        accessMode=AccessMode.READONLY,
    )

    state = String(
        displayedName="State",
        enum=State,
        displayType='State',
        description="The current state the device is in",
        accessMode=AccessMode.READONLY, assignment=Assignment.OPTIONAL,
        defaultValue=State.UNKNOWN,
    )

    status = String(
        displayedName="Status",
        description="A more detailed status description",
        accessMode=AccessMode.READONLY, assignment=Assignment.OPTIONAL,
        defaultValue="",
    )

    alarmCondition = String(
        enum=AlarmCondition,
        displayedName="Alarm condition",
        displayType="AlarmCondition",
        description="The current alarm condition of the device.",
        accessMode=AccessMode.READONLY,
        defaultValue=AlarmCondition.NONE)

    @property
    def globalAlarmCondition(self):
        """Backward compatible property for the legacy alarm implementation"""
        return self.alarmCondition

    @globalAlarmCondition.setter
    def globalAlarmCondition(self, value):
        """Backward compatible alarm setter for the legacy alarms"""
        self.alarmCondition = value

    lockedBy = String(
        displayedName="Locked by",
        description="The name of the device holding a lock on this one "
                    "(empty if not locked)",
        displayType="lockedBy",
        accessMode=AccessMode.RECONFIGURABLE, assignment=Assignment.OPTIONAL,
        defaultValue="",
    )

    @Slot(displayedName="Clear Lock", requiredAccessLevel=AccessLevel.EXPERT,
          description="Clear the lock on this device")
    async def slotClearLock(self):
        """ Clear the lock on this device """
        self.lockedBy = ""

    lastCommand = String(
        displayedName="Last command",
        defaultValue="",
        description="The last slot called.",
        accessMode=AccessMode.READONLY,
        requiredAccessLevel=AccessLevel.EXPERT,
    )

    log = Node(
        build_logger_node(),
        description="Logging settings",
        displayedName="Logger",
        requiredAccessLevel=AccessLevel.EXPERT)

    signalChanged = Signal(TypeHash(), String())
    signalSchemaUpdated = Signal(TypeSchema(), String())

    @property
    def is_initialized(self):
        """Check if the device is online and has passed onInitialization"""
        return super().is_initialized

    def getLocalDevice(self, instanceId):
        """Returns the instance of a device if present in the local server

        This device instance can be used to shortcut broker communication.

        This returns a strong reference::

            async def onInitialization(self):
                device = self.getLocalDevice(instanceId)

        :returns: Device instance or `None` if not found.

        Note: Added in Karabo 2.15.
        """
        server = super().device_server
        return server.deviceInstanceMap.get(instanceId)

    def __init__(self, configuration):
        self.__timeServerId = configuration.pop("__timeServerId", "None")
        super().__init__(configuration)
        if not isSet(self.serverId):
            self.serverId = self._serverId_
        if not isSet(self.hostName):
            self.hostName = socket.gethostname().partition('.')[0]

        self.classId = type(self).__name__
        # class version is the package name plus version
        # the latter should be overwritten in inheriting classes
        classPackage = self.__module_orig__.split('.', 1)[0]
        self.classVersion = f"{classPackage}-{type(self).__version__}"
        self.karaboVersion = karaboVersion
        self.pid = os.getpid()
        self._statusInfo = self._get_instance_info_status()
        self._cachedSchema = self.getDeviceSchema()

    def _get_instance_info_status(self):
        if self.state == State.UNKNOWN:
            status = "unknown"
        elif self.state == State.ERROR:
            status = "error"
        else:
            status = "ok"
        return status

    def _initInfo(self):
        info = super()._initInfo()
        info["type"] = "device"
        info["classId"] = self.classId.value
        info["serverId"] = self.serverId.value
        info["host"] = self.hostName
        info["status"] = self._statusInfo

        # device capabilities are encoded in a bit mask field
        capabilities = 0
        if hasattr(self, "availableScenes"):
            capabilities |= Capabilities.PROVIDES_SCENES
        if hasattr(self, "availableMacros"):
            capabilities |= Capabilities.PROVIDES_MACROS
        if hasattr(self, "interfaces"):
            capabilities |= Capabilities.PROVIDES_INTERFACES

        info["capabilities"] = capabilities

        interfaces = 0
        if hasattr(self, "interfaces"):
            for description in self.interfaces.value:
                if description in Interfaces.__members__:
                    interfaces |= Interfaces[description]
                else:
                    raise NotImplementedError(
                        "Provided interface is not supported: {}".format(
                            description))

            info["interfaces"] = interfaces

        return info

    async def _run(self, **kwargs):
        self._sigslot.enter_context(self.log.setBroker(self._sigslot))
        await super()._run(**kwargs)
        # Logging mechanism will add the deviceId to the log message
        self.logger.info("Device is up and running.")

    @slot
    def slotGetConfiguration(self):
        return self.configurationAsHash(), self.deviceId

    @slot
    def slotGetConfigurationSlice(self, info):
        """Public slot to retrieve the values of a list of `paths`

        :param info: Hash with {key: `paths`, value: list of strings}

        returns: A Hash with the values and attributes for each requested
        property key.
        """
        return get_property_hash(self, info["paths"])

    def __get_time_info(self):
        """Internal method to get the time information"""
        h = Hash("timeServerId", self.__timeServerId)
        timestamp = get_timestamp().toDict()
        reference = TimeMixin.toDict()
        h.setElement("time", True, timestamp)
        h.setElement("reference", True, reference)
        return h

    @slot
    def slotGetTime(self, info=None):
        """Return the actual time information of this device

        This slot call return a Hash with key ``time`` and the attributes
        provide an actual timestamp with train Id information.

        The slot call further provides a reference time information via key
        ``reference`` and the attributes provide the train id.

        This method has an empty input argument to allow a generic protocol.
        """
        return self.__get_time_info()

    @slot
    def slotGetSystemInfo(self, info=None):
        """Return the actual system information of this device"""
        info = self.__get_time_info()
        h = Hash("timeInfo", info)
        h["broker"] = str(self._sigslot.connection.url)
        h["user"] = getpass.getuser()
        return h

    def _checkLocked(self, message):
        """return an error message if device is locked or None if not"""
        lock_clear = ("slotClearLock"
                      in self._sigslot.get_property(message, "slotFunctions"))
        if (self.lockedBy and self.lockedBy !=
                self._sigslot.get_property(message, "signalInstanceId") and
                not lock_clear):
            return f'Device locked by "{self.lockedBy}"'
        return None

    async def slotReconfigure(self, reconfiguration, message):
        # This can only be called as a slot
        msg = self._checkLocked(message)
        if msg is not None:
            raise KaraboError(msg)
        caller = self._sigslot.get_property(message, "signalInstanceId")
        try:
            await super().slotReconfigure(reconfiguration)
        finally:
            self.lastCommand = f"slotReconfigure <- {caller}"
            self.update()

    slotReconfigure = slot(slotReconfigure, passMessage=True)

    def update(self):
        """Update the instanceInfo Hash according to the status info
        """
        statusInfo = self._get_instance_info_status()
        if statusInfo != self._statusInfo:
            self.updateInstanceInfo(Hash("status", statusInfo))
            self._statusInfo = statusInfo
        super().update()

    async def setOutputSchema(self, *args):
        """This is a wrapper function to change the schema of existing output
        channels.

        For each output channel that is to be changed its key name and the
        new Schema have to be provided, e.g.

            await self.setOutputSchema("output1", schema1,
                                       "output2", schema2,
                                       ...)

        Note: The existing output channel is closed while changing the schema.
        """
        if len(args) % 2 > 0:
            raise RuntimeError("Arguments passed in setOutputSchema "
                               "need to be pairs")

        async def _build_output(key, schema):
            """Rebuild a single output channel for `key` and `schema`"""
            assert "." not in key, "Nested output channels are not allowed!"

            channel_desc = getattr(type(self), key, None)
            assert type(channel_desc) is OutputChannel
            _, attrs = channel_desc.toSchemaAndAttrs(None, None)
            # Obtain previous configuration. This is safe
            # since output channel schema nodes do not have a configuration
            channel = getattr(self, key)
            config_hash = channel.configurationAsHash()
            config = {k: v for k, v in config_hash.items()}
            # Note: We have to close the output channel and all related socket
            # connections! This will lead to all input channels closing
            # and asking for a new schema!
            await channel.close()
            output_channel = OutputChannel(schema, strict=False, **attrs)
            setattr(self.__class__, key, output_channel)

            return config

        configurations = []
        for key, schema in zip(args[::2], args[1::2]):
            config = await _build_output(key, schema)
            configurations.extend([key, config])

        await self.publishInjectedParameters(*configurations)

    @slot
    def slotGetSchema(self, onlyCurrentState):
        if onlyCurrentState:
            return self.getDeviceSchema(state=self.state), self.deviceId
        else:
            return self._cachedSchema, self.deviceId

    def _notifyNewSchema(self):
        """Notfiy the network that our schema has changed"""
        self._cachedSchema = self.getDeviceSchema()
        self.signalSchemaUpdated(self._cachedSchema, self.deviceId)

globalAlarmCondition property writable

Backward compatible property for the legacy alarm implementation

is_initialized property

Check if the device is online and has passed onInitialization

__get_time_info()

Internal method to get the time information

Source code in src/pythonKarabo/karabo/middlelayer/device.py
270
271
272
273
274
275
276
277
def __get_time_info(self):
    """Internal method to get the time information"""
    h = Hash("timeServerId", self.__timeServerId)
    timestamp = get_timestamp().toDict()
    reference = TimeMixin.toDict()
    h.setElement("time", True, timestamp)
    h.setElement("reference", True, reference)
    return h

getLocalDevice(instanceId)

Returns the instance of a device if present in the local server

This device instance can be used to shortcut broker communication.

This returns a strong reference::

async def onInitialization(self):
    device = self.getLocalDevice(instanceId)

:returns: Device instance or None if not found.

Note: Added in Karabo 2.15.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def getLocalDevice(self, instanceId):
    """Returns the instance of a device if present in the local server

    This device instance can be used to shortcut broker communication.

    This returns a strong reference::

        async def onInitialization(self):
            device = self.getLocalDevice(instanceId)

    :returns: Device instance or `None` if not found.

    Note: Added in Karabo 2.15.
    """
    server = super().device_server
    return server.deviceInstanceMap.get(instanceId)

setOutputSchema(*args) async

This is a wrapper function to change the schema of existing output channels.

For each output channel that is to be changed its key name and the new Schema have to be provided, e.g.

await self.setOutputSchema("output1", schema1,
                           "output2", schema2,
                           ...)

Note: The existing output channel is closed while changing the schema.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
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
async def setOutputSchema(self, *args):
    """This is a wrapper function to change the schema of existing output
    channels.

    For each output channel that is to be changed its key name and the
    new Schema have to be provided, e.g.

        await self.setOutputSchema("output1", schema1,
                                   "output2", schema2,
                                   ...)

    Note: The existing output channel is closed while changing the schema.
    """
    if len(args) % 2 > 0:
        raise RuntimeError("Arguments passed in setOutputSchema "
                           "need to be pairs")

    async def _build_output(key, schema):
        """Rebuild a single output channel for `key` and `schema`"""
        assert "." not in key, "Nested output channels are not allowed!"

        channel_desc = getattr(type(self), key, None)
        assert type(channel_desc) is OutputChannel
        _, attrs = channel_desc.toSchemaAndAttrs(None, None)
        # Obtain previous configuration. This is safe
        # since output channel schema nodes do not have a configuration
        channel = getattr(self, key)
        config_hash = channel.configurationAsHash()
        config = {k: v for k, v in config_hash.items()}
        # Note: We have to close the output channel and all related socket
        # connections! This will lead to all input channels closing
        # and asking for a new schema!
        await channel.close()
        output_channel = OutputChannel(schema, strict=False, **attrs)
        setattr(self.__class__, key, output_channel)

        return config

    configurations = []
    for key, schema in zip(args[::2], args[1::2]):
        config = await _build_output(key, schema)
        configurations.extend([key, config])

    await self.publishInjectedParameters(*configurations)

slotClearLock() async

Clear the lock on this device

Source code in src/pythonKarabo/karabo/middlelayer/device.py
144
145
146
147
148
@Slot(displayedName="Clear Lock", requiredAccessLevel=AccessLevel.EXPERT,
      description="Clear the lock on this device")
async def slotClearLock(self):
    """ Clear the lock on this device """
    self.lockedBy = ""

slotGetConfigurationSlice(info)

Public slot to retrieve the values of a list of paths

:param info: Hash with {key: paths, value: list of strings}

returns: A Hash with the values and attributes for each requested property key.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
259
260
261
262
263
264
265
266
267
268
@slot
def slotGetConfigurationSlice(self, info):
    """Public slot to retrieve the values of a list of `paths`

    :param info: Hash with {key: `paths`, value: list of strings}

    returns: A Hash with the values and attributes for each requested
    property key.
    """
    return get_property_hash(self, info["paths"])

slotGetSystemInfo(info=None)

Return the actual system information of this device

Source code in src/pythonKarabo/karabo/middlelayer/device.py
293
294
295
296
297
298
299
300
@slot
def slotGetSystemInfo(self, info=None):
    """Return the actual system information of this device"""
    info = self.__get_time_info()
    h = Hash("timeInfo", info)
    h["broker"] = str(self._sigslot.connection.url)
    h["user"] = getpass.getuser()
    return h

slotGetTime(info=None)

Return the actual time information of this device

This slot call return a Hash with key time and the attributes provide an actual timestamp with train Id information.

The slot call further provides a reference time information via key reference and the attributes provide the train id.

This method has an empty input argument to allow a generic protocol.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
279
280
281
282
283
284
285
286
287
288
289
290
291
@slot
def slotGetTime(self, info=None):
    """Return the actual time information of this device

    This slot call return a Hash with key ``time`` and the attributes
    provide an actual timestamp with train Id information.

    The slot call further provides a reference time information via key
    ``reference`` and the attributes provide the train id.

    This method has an empty input argument to allow a generic protocol.
    """
    return self.__get_time_info()

update()

Update the instanceInfo Hash according to the status info

Source code in src/pythonKarabo/karabo/middlelayer/device.py
326
327
328
329
330
331
332
333
def update(self):
    """Update the instanceInfo Hash according to the status info
    """
    statusInfo = self._get_instance_info_status()
    if statusInfo != self._statusInfo:
        self.updateInstanceInfo(Hash("status", statusInfo))
        self._statusInfo = statusInfo
    super().update()

Bases: Device

Keep track of other devices

A :class:~karabo.middlelayer.Device which also inherits from this class keeps track of all the other devices in this Karabo installation. Without inheriting from this class, listing other devices is impossible.

Source code in src/pythonKarabo/karabo/middlelayer/device.py
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
class DeviceClientBase(Device):
    """Keep track of other devices

    A :class:`~karabo.middlelayer.Device` which also inherits from this class
    keeps track of all the other devices in this Karabo installation. Without
    inheriting from this class, listing other devices is impossible."""
    abstract = True
    wait_topology = True

    def __init__(self, configuration: dict):
        # "unknown" is default type for bare C++ SignalSlotable
        self.systemTopology = Hash("device", Hash(), "server", Hash(),
                                   "macro", Hash(), "client", Hash(),
                                   "unknown", Hash())
        super().__init__(configuration)

    async def _run(self, **kwargs):
        await super()._run(**kwargs)
        await self._sigslot.async_emit(
            "call", {"*": ["slotDiscover"]}, self.deviceId)
        # We are collecting all the instanceInfo's and wait for their arrival
        # before the device comes online.
        # Some clients, such as ikarabo, don't want to wait this additional
        # time
        if self.wait_topology:
            await sleep(3)

    @slot
    async def slotInstanceNew(self, instanceId: str, info: Hash):
        self.removeServerChildren(instanceId, info)
        self.updateSystemTopology(instanceId, info)
        await super().slotInstanceNew(instanceId, info)

    @slot
    def slotInstanceUpdated(self, instanceId: str, info: Hash):
        self.updateSystemTopology(instanceId, info)
        super().slotInstanceUpdated(instanceId, info)

    @slot
    def slotInstanceGone(self, instanceId: str, info: Hash):
        self.removeServerChildren(instanceId, info)
        self.systemTopology[info["type"]].pop(instanceId, None)
        return super().slotInstanceGone(instanceId, info)

    @slot
    def slotDiscoverAnswer(self, deviceId: str, info: Hash):
        self.updateSystemTopology(deviceId, info)

    def removeServerChildren(self, instanceId, info):
        """Cleanup the device children from the server
        """
        if info["type"] == "server":
            devices = [k for k, v, a in self.systemTopology["device"].iterall()
                       if a["serverId"] == instanceId]
            for deviceId in devices:
                self.systemTopology["device"].pop(deviceId, None)

    def updateSystemTopology(self, instanceId: str, info: Hash):
        """Update the systemTopology Hash with new information from `info`"""
        h = Hash()
        h.setElement(
            f"{info['type']}.{instanceId}", Hash(), dict(info.items()))
        self.systemTopology.merge(h)
        return h

removeServerChildren(instanceId, info)

Cleanup the device children from the server

Source code in src/pythonKarabo/karabo/middlelayer/device.py
441
442
443
444
445
446
447
448
def removeServerChildren(self, instanceId, info):
    """Cleanup the device children from the server
    """
    if info["type"] == "server":
        devices = [k for k, v, a in self.systemTopology["device"].iterall()
                   if a["serverId"] == instanceId]
        for deviceId in devices:
            self.systemTopology["device"].pop(deviceId, None)

updateSystemTopology(instanceId, info)

Update the systemTopology Hash with new information from info

Source code in src/pythonKarabo/karabo/middlelayer/device.py
450
451
452
453
454
455
456
def updateSystemTopology(self, instanceId: str, info: Hash):
    """Update the systemTopology Hash with new information from `info`"""
    h = Hash()
    h.setElement(
        f"{info['type']}.{instanceId}", Hash(), dict(info.items()))
    self.systemTopology.merge(h)
    return h

Bases: Exception

A :class:KaraboError is raised if an error occurs which is specific to Karabo. This is mostly because things went wrong on the other end of a network connection.

Source code in src/pythonKarabo/karabo/native/exceptions.py
19
20
21
22
23
24
25
26
27
class KaraboError(Exception):
    """A :class:`KaraboError` is raised if an error occurs which is
    specific to Karabo. This is mostly because things went wrong on
    the other end of a network connection.
    """

    def __init__(self, *args, loglevel=logging.ERROR, **kwargs):
        super().__init__(*args, **kwargs)
        self.loglevel = loglevel

Synchronization

Asynchronous helpers and primitives:

execute a task in the background

run the task in the background. This returns a :class:KaraboFuture, which may be cancelled or waited for as needed.

A timeout may be given after which the function in the background will be cancelled.

Example::

def do_something_in_background(some_value):
    # so something here

task = background(do_something_in_background, "some value")
task.cancel()
Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
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
def background(task, *args, timeout=-1):
    """execute a *task* in the background

    run the *task* in the background. This returns a :class:`KaraboFuture`,
    which may be cancelled or waited for as needed.

    A *timeout* may be given after which the function in the background will
    be cancelled.

    Example::

        def do_something_in_background(some_value):
            # so something here

        task = background(do_something_in_background, "some value")
        task.cancel()
    """

    @synchronize
    async def inner(task, *args):
        loop = get_event_loop()
        try:
            if iscoroutine(task):
                assert not args
                return (await task)
            else:
                return (await loop.run_coroutine_or_thread(task, *args))
        except CancelledError:
            raise
        except Exception:
            try:
                logger = logging.getLogger(loop.instance().deviceId)
            except BaseException:
                # Make absolutely sure that this the log message is done!
                logger = logging.getLogger()
            logger.exception("Error in background task ...")
            raise

    ret = inner(task, *args, wait=False, timeout=timeout)
    if iscoroutine(ret):
        task = ensure_future(ret)
        return task
    else:
        return ret

wait until all KaraboFutures given are done

This function waits until all :class:KaraboFutures passed as arguments are done. The function returns a list of the return value of all functions.

If one of the futures raises an exception, gather also immediately raises that exception, unless return_exceptions indicates that all futures should be waited for, and the exception returned instead.

Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@synchronize
async def gather(*args, return_exceptions=False):
    """wait until all KaraboFutures given are done

    This function waits until all :class:`KaraboFuture`s passed as
    arguments are done. The function returns a list of the return value
    of all functions.

    If one of the futures raises an exception, gather also immediately raises
    that exception, unless *return_exceptions* indicates that all futures
    should be waited for, and the exception returned instead.
    """
    assert all(isinstance(f, KaraboFuture) for f in args), \
        "Arguments must be of type KaraboFuture, for `asyncio.Futures` "\
        " please use `asyncio.gather` instead"
    return (await asyncio.gather(*[f.future for f in args],
                                 return_exceptions=return_exceptions))

wait for the first of coroutines or futures to return

Wait for the first future given as keyword argument to return. It returns three dicts: one with the names of the done futures and their results, one mapping the name of the pending futures to them, and a third one mapping the names of failed futures to their exceptions, as an example::

work = background(do_some_work)
party = background(partey)
sleep = background(zzz)
done, pending, error = firstCompleted(
    work=work, party=party, sleep=sleep)

the result will then be something like {"work": 5}, {"party": Future()} and {"sleep": RuntimeError()}.

You may also give futures as positional parameters, they will be returned by their number::

done, pending, error = firstCompleted(work, party, sleep)

will return something like {0: 5}, {1: Future()} and {2: RuntimeError()}.

Cancelled futures will be listed in the error dict, mapped to None. You may also give a timemout. Futures still pending will be cancelled before return, unless you set cancel_pending to False.

Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
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
@synchronize_notimeout
async def firstCompleted(*args, **kwargs):
    """wait for the first of coroutines or futures to return

    Wait for the first future given as keyword argument to return. It returns
    three dicts: one with the names of the done futures and their results,
    one mapping the name of the pending futures to them, and a third one
    mapping the names of failed futures to their exceptions, as an example::

        work = background(do_some_work)
        party = background(partey)
        sleep = background(zzz)
        done, pending, error = firstCompleted(
            work=work, party=party, sleep=sleep)

    the result will then be something like ``{"work": 5}``,
    ``{"party": Future()}`` and ``{"sleep": RuntimeError()}``.

    You may also give futures as positional parameters, they will be returned
    by their number::

        done, pending, error = firstCompleted(work, party, sleep)

    will return something like ``{0: 5}``, ``{1: Future()}`` and
    ``{2: RuntimeError()}``.

    Cancelled futures will be listed in the error dict, mapped to ``None``.
    You may also give a timemout.
    Futures still pending will be cancelled before return, unless you
    set `cancel_pending` to `False`.
    """
    return (await _wait(asyncio.FIRST_COMPLETED, *args, **kwargs))

wait until all futures are done

This function is an improved version of :func:asyncio.gather, which also works if a future fails, and a timeout can be set.

For the returned dicts, see :func:firstCompleted.

Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
186
187
188
189
190
191
192
193
194
195
@synchronize_notimeout
async def allCompleted(*args, **kwargs):
    """wait until all futures are done

    This function is an improved version of :func:`asyncio.gather`,
    which also works if a future fails, and a timeout can be set.

    For the returned dicts, see :func:`firstCompleted`.
    """
    return (await _wait(asyncio.ALL_COMPLETED, *args, **kwargs))

wait until a future raises an exception

If no future raises an exception, this is the same as :func:allCompleted.

This function is an improved version of :func:asyncio.gather, which also works if a future fails, and a timeout can be set.

For the returned dicts, see :func:firstCompleted.

Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
198
199
200
201
202
203
204
205
206
207
208
209
210
@synchronize_notimeout
async def firstException(*args, **kwargs):
    """wait until a future raises an exception

    If no future raises an exception, this is the same as
    :func:`allCompleted`.

    This function is an improved version of :func:`asyncio.gather`,
    which also works if a future fails, and a timeout can be set.

    For the returned dicts, see :func:`firstCompleted`.
    """
    return (await _wait(asyncio.FIRST_EXCEPTION, *args, **kwargs))

do nothing for delay seconds

if delay is a :class:~karabo.middlelayer.KaraboValue, its unit is respected.

This method should be preferred over :func:time.sleep, as it is interruptable.

Source code in src/pythonKarabo/karabo/middlelayer/synchronization.py
103
104
105
106
107
108
109
110
111
112
113
114
@synchronize_notimeout
async def sleep(delay, result=None):
    """do nothing for *delay* seconds

    if *delay* is a :class:`~karabo.middlelayer.KaraboValue`, its unit is
    respected.

    This method should be preferred over :func:`time.sleep`, as it is
    interruptable."""
    if isinstance(delay, KaraboValue):
        delay /= unit.second
    return (await asyncio.sleep(delay, result))

A handle for a result that will be available in the future

This will be returned by many Karabo methods, if a callback has been set to a function or None. It is possible to add more callbacks, to wait for completion, or cancel the operation in question.

Source code in src/pythonKarabo/karabo/middlelayer/eventloop.py
 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
class KaraboFuture:
    """A handle for a result that will be available in the future

    This will be returned by many Karabo methods, if a callback has been
    set to a function or None. It is possible to add more callbacks, to
    wait for completion, or cancel the operation in question.
    """

    def __init__(self, future):
        self.future = ensure_future(future)

    @synchronize
    def add_done_callback(self, fn):
        """Add another callback to the future"""
        loop = get_event_loop()
        instance = loop.instance()

        def func(future):
            loop.create_task(loop.run_coroutine_or_thread(
                fn, self), instance)

        self.future.add_done_callback(func)

    @synchronize
    async def wait(self):
        """Wait for the result to be available, and return the result"""
        return await self.future

add_done_callback(fn)

Add another callback to the future

Source code in src/pythonKarabo/karabo/middlelayer/eventloop.py
 99
100
101
102
103
104
105
106
107
108
109
@synchronize
def add_done_callback(self, fn):
    """Add another callback to the future"""
    loop = get_event_loop()
    instance = loop.instance()

    def func(future):
        loop.create_task(loop.run_coroutine_or_thread(
            fn, self), instance)

    self.future.add_done_callback(func)

wait() async

Wait for the result to be available, and return the result

Source code in src/pythonKarabo/karabo/middlelayer/eventloop.py
111
112
113
114
@synchronize
async def wait(self):
    """Wait for the result to be available, and return the result"""
    return await self.future

return a context manager to lock a device

This allows to lock another devices for exclusive use::

with (await lock(proxy)):
    #do stuff

In a synchronous context, this function waits at the end of the block until the lock is released, unless wait_for_release is False. In an asynchronous context, we cannot wait for release, so we don't.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
@synchronize
async def lock(proxy, wait_for_release=None):
    """return a context manager to lock a device

    This allows to lock another devices for exclusive use::

        with (await lock(proxy)):
            #do stuff

    In a synchronous context, this function waits at the end of the block
    until the lock is released, unless *wait_for_release* is *False*. In an
    asynchronous context, we cannot wait for release, so we don't.
    """

    myId = get_instance().deviceId
    if proxy._lock_count == 0:
        if proxy.lockedBy == myId:
            # we just unlocked the device but didn't get a response yet
            await proxy._update()
        while proxy.lockedBy != myId:
            if proxy.lockedBy == "":
                proxy.lockedBy = myId
            await waitUntilNew(proxy.lockedBy)

    @contextmanager
    def context():
        proxy._lock_count += 1
        try:
            yield
        finally:
            proxy._lock_count -= 1
            if proxy._lock_count == 0:
                if wait_for_release is False:
                    setNoWait(proxy, lockedBy="")
                else:
                    proxy.lockedBy = ""

    return context()

Synchronization Primitives

Blocking until conditions are met:

Wait until the condition is True

The condition is typically a lambda function, as in::

waitUntil(lambda: device.speed > 3)

The condition will be evaluated each time something changes. Note that for this to work, it is necessary that all the devices used in the condition are connected while we are waiting (so typically they appear in a with statement)

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
@synchronize
async def waitUntil(condition):
    """Wait until the condition is True

    The condition is typically a lambda function, as in::

        waitUntil(lambda: device.speed > 3)

    The condition will be evaluated each time something changes. Note
    that for this to work, it is necessary that all the devices used in the
    condition are connected while we are waiting (so typically they appear
    in a with statement)"""
    loop = get_event_loop()
    # suspend once to assure the event loop gets a chance to run
    await sleep(0)
    while not condition():
        await loop.waitForChanges()

Wait until new futures provide a result

The futures can be provided as in::

waitUntilNew(device.speed, device.targetPosition)

where device is a proxy. The first completed future will return the function. This function can be used to wait for a global property event on a device as well::

waitUntilNew(device)

Note that for this to work, it is necessary that all the devices used in the future parameters are connected while we are waiting.

For the asynchronous case a :func:asyncio.wait_for should be combined if a timeout is desired.

Source code in src/pythonKarabo/karabo/middlelayer/device_client.py
 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
@synchronize
async def waitUntilNew(*props):
    """Wait until new futures provide a result

    The futures can be provided as in::

        waitUntilNew(device.speed, device.targetPosition)

    where device is a proxy. The first completed future will return the
    function. This function can be used to wait for a global property event
    on a device as well::

        waitUntilNew(device)

    Note that for this to work, it is necessary that all the devices used in
    the future parameters are connected while we are waiting.

    For the asynchronous case a :func:`asyncio.wait_for` should be combined
    if a `timeout` is desired.
    """
    futures = []
    for prop in props:
        if isinstance(prop, ProxyBase):
            future = OneShotQueue(loop=prop._device._sigslot.loop)
            prop._queues[None].add(future)
        else:
            proxy = prop._parent
            future = OneShotQueue(loop=proxy._device._sigslot.loop)
            proxy._queues[prop.descriptor.longkey].add(future)
        futures.append(future)
    await firstCompleted(*futures)

Karabo Descriptors

Descriptor Categories

Abstract base classes for Karabo types:

Bases: Descriptor

A Type is a descriptor that does not contain other descriptors.

All basic Karabo types are described by a Type.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
class Type(Descriptor):
    """A Type is a descriptor that does not contain other descriptors.

    All basic Karabo types are described by a Type.
    """
    unitSymbol = Attribute(Unit.NUMBER, dtype=Unit)
    metricPrefixSymbol = Attribute(MetricPrefix.NONE, dtype=MetricPrefix)
    enum = None

    # Note: Require lookup dicts!
    types = [None] * 51
    fromname = {}
    strs = {}

    options = Attribute()

    def __init__(self, strict=True, **kwargs):
        super().__init__(strict=strict, **kwargs)
        self.units = QuantityValue(
            1, unit=self.unitSymbol, metricPrefix=self.metricPrefixSymbol
        ).units
        self.dimensionality = self.units.dimensionality

        # re-write the options. We first delete them so we don't check
        # against ourselves while generating the options
        options = self.options
        self.options = None
        if options is not None:
            if self.enum is None:
                self.options = [self.toKaraboValue(o, strict=False).value
                                for o in options]
            else:
                self.options = [self.toKaraboValue(o, strict=strict).enum
                                for o in options]

    def toKaraboValue(self, data, strict=True):
        """Convert data into a KaraboValue

        in strict mode, the default, we only allow values which have the
        correct unit set or are of the correct enum. In non-strict mode,
        we simply add our unit if none is given, or convert to our enum.
        This is important for data coming from the network, as that has no
        notion about units or enums.

        Note that for critical applications, it is advisable to do unit
        conversions and timestamp handling by hand.
        """
        raise NotImplementedError

    def check(self, ret):
        if self.options is not None and ret not in self.options:
            raise ValueError(f"value {ret} not in options {self.options}"
                             f" for key {self.key}")

    @classmethod
    def fromstring(cls, s):
        return hashtype_from_string(cls.number, s)

    @classmethod
    def hashname(cls):
        return cls._hashname

    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if "number" in cls.__dict__:
            cls.types[cls.number] = cls
            s = ''
            lastlower = False
            for c in cls.__name__:
                if c.isupper() and lastlower:
                    s += '_'
                s += c.capitalize()
                lastlower = c.islower()
            cls._hashname = s.rstrip('_')
            cls.fromname[cls.hashname()] = cls
        if 'numpy' in cls.__dict__:
            cls.strs[np.dtype(cls.numpy).str] = cls

    def toDataAndAttrs(self, data):
        if not isinstance(data, KaraboValue):
            return self.cast(data), {}
        if data.timestamp is not None:
            attrs = data.timestamp.toDict()
        else:
            attrs = {}
        return data.value, attrs

    def toSchemaAndAttrs(self, device, state):
        h, attrs = super().toSchemaAndAttrs(device, state)
        attrs["nodeType"] = NodeType.Leaf
        attrs["valueType"] = self.hashname()
        return h, attrs

    def __call__(self, method):
        if self.description is None:
            self.description = method.__doc__
        self.setter = method
        return self

toKaraboValue(data, strict=True)

Convert data into a KaraboValue

in strict mode, the default, we only allow values which have the correct unit set or are of the correct enum. In non-strict mode, we simply add our unit if none is given, or convert to our enum. This is important for data coming from the network, as that has no notion about units or enums.

Note that for critical applications, it is advisable to do unit conversions and timestamp handling by hand.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
699
700
701
702
703
704
705
706
707
708
709
710
711
def toKaraboValue(self, data, strict=True):
    """Convert data into a KaraboValue

    in strict mode, the default, we only allow values which have the
    correct unit set or are of the correct enum. In non-strict mode,
    we simply add our unit if none is given, or convert to our enum.
    This is important for data coming from the network, as that has no
    notion about units or enums.

    Note that for critical applications, it is advisable to do unit
    conversions and timestamp handling by hand.
    """
    raise NotImplementedError

Bases: Type

This is the base class for all vectors of data

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
class Vector(Type):
    """This is the base class for all vectors of data"""
    minSize = Attribute(dtype=np.uint32)
    maxSize = Attribute(dtype=np.uint32)

    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if "basetype" in cls.__dict__:
            cls.basetype.vectortype = cls

    def check(self, ret):
        if self.minSize is not None and len(ret) < self.minSize:
            raise ValueError("Vector {} of {} with size {} is shorter than "
                             "the allowed size of {}".format(
                                 ret, self.key, len(ret), self.minSize))
        if self.maxSize is not None and len(ret) > self.maxSize:
            raise ValueError("Vector {} of {} with size {} is larger than "
                             "the allowed size of {}".format(
                                 ret, self.key, len(ret), self.maxSize))
        super().check(ret)

Bases: Vector

The base class for all vectors which can be represented as numpy vectors

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
class NumpyVector(Vector):
    """The base class for all vectors which can be represented as numpy
    vectors"""
    vstrs = {}
    numpy = np.object_

    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.vstrs[cls.basetype.numpy().dtype.str] = cls

    def cast(self, other):
        if (isinstance(other, np.ndarray) and
                other.dtype == self.basetype.numpy):
            ret = other
        else:
            ret = np.array(other, dtype=self.basetype.numpy)
        assert ret.ndim == 1, "can only treat one-dimensional vectors"

        self.check(ret)
        return ret

    def toKaraboValue(self, data, strict=True):
        if not isinstance(data, KaraboValue):
            data = self.cast(data)
        data = QuantityValue(data, descriptor=self)
        if data.units != self.units:
            data = data.to(self.units)
            data.descriptor = self
        self.check(data)

        return data

This is the base for all numeric types

It features the minimum and maximum for its values given in minExc, maxExc, minInc, and maxInc, where min and max stand for miniumum and maximum, while Exc and Inc stand for exclusive and inclusive, respectively.

Do not use inclusive and exclusive for the same limit at the same time.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
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
class Simple:
    """This is the base for all numeric types

    It features the minimum and maximum for its values given
    in ``minExc``, ``maxExc``, ``minInc``, and ``maxInc``, where
    ``min`` and ``max`` stand for miniumum and maximum, while ``Exc``
    and ``Inc`` stand for exclusive and inclusive, respectively.

    Do not use inclusive and exclusive for the same limit at the same
    time.
    """

    # For the `Simple` classes, the `dtype` to be defined by subclasses
    minExc = Attribute(dtype=Undefined)
    maxExc = Attribute(dtype=Undefined)
    minInc = Attribute(dtype=Undefined)
    maxInc = Attribute(dtype=Undefined)

    def cast(self, other):
        if self.enum is not None:
            ret = super().cast(other)
        elif isinstance(other, self.numpy):
            ret = other
        else:
            ret = self.numpy(other)
        self.check(ret)
        return ret

    def check(self, ret):
        if (self.minExc is not None and ret <= self.minExc or
                self.minInc is not None and ret < self.minInc or
                self.maxExc is not None and ret >= self.maxExc or
                self.maxInc is not None and ret > self.maxInc):
            raise ValueError("value {} of {} not in allowed range".
                             format(ret, self.key))
        super().check(ret)

    def toKaraboValue(self, data, strict=True):
        if self.enum is not None:
            return Enumable.toKaraboValue(self, data, strict)
        if isinstance(data, KaraboValue):
            timestamp = data.timestamp
        else:
            timestamp = None
        if isinstance(data, str):
            data = QuantityValue(data, descriptor=self)
        if isinstance(data, QuantityValue):
            data = data.to(self.units).value
        self.check(data)
        data = QuantityValue(self.numpy(data), descriptor=self,
                             timestamp=timestamp)
        return data

    def getMinMax(self):
        """Return a tuple (minimum, maximum) for this value

        This are the minimum and maximum value this type can take.
        This is based both on the definition of the type, as well as
        on the attributes ``minInc``, ``maxInc``, ``minExc``,
        ``maxExc``. All information can be condensed in just two
        values as it is futile to specify both inclusive and exclusive
        values for the same limit.
        """
        raise NotImplementedError

    def toSchemaAndAttrs(self, device, state):
        schema, attrs = super().toSchemaAndAttrs(device, state)
        if self.options is not None:
            # assure options are serialized correctly
            opts = self.options
            if self.enum is not None:
                opts = [opt.value for opt in self.options]
            attrs["options"] = np.array(opts, dtype=self.numpy)
        return schema, attrs

getMinMax()

Return a tuple (minimum, maximum) for this value

This are the minimum and maximum value this type can take. This is based both on the definition of the type, as well as on the attributes minInc, maxInc, minExc, maxExc. All information can be condensed in just two values as it is futile to specify both inclusive and exclusive values for the same limit.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
234
235
236
237
238
239
240
241
242
243
244
def getMinMax(self):
    """Return a tuple (minimum, maximum) for this value

    This are the minimum and maximum value this type can take.
    This is based both on the definition of the type, as well as
    on the attributes ``minInc``, ``maxInc``, ``minExc``,
    ``maxExc``. All information can be condensed in just two
    values as it is futile to specify both inclusive and exclusive
    values for the same limit.
    """
    raise NotImplementedError

Bases: Simple

The base class for all floating-point types

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
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
class Number(Simple):
    """The base class for all floating-point types"""

    absoluteError = Attribute()
    relativeError = Attribute()

    def __init__(self, strict=True, **kwargs):
        super().__init__(strict=strict, **kwargs)
        abs_err = self.absoluteError
        if abs_err is not None and abs_err <= 0:
            raise KaraboError(f"Wrong absolute error specified {abs_err} "
                              f"for {self.key}")
        rel_err = self.relativeError
        if rel_err is not None and rel_err <= 0:
            raise KaraboError(f"Wrong relative error specified {rel_err} "
                              f"for {self.key}")

    def getMinMax(self):
        info = np.finfo(self.numpy)
        min = self.minExc
        if min is None:
            min = self.minInc
        else:
            min = min * (1 + np.sign(min) * info.eps) + info.tiny
        if min is None:
            min = info.min

        max = self.maxExc
        if max is None:
            max = self.maxInc
        else:
            max = max * (1 - np.sign(max) * info.eps) - info.tiny
        if max is None:
            max = info.max

        return min, max

Bases: Simple, Enumable

The base class for all integers

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
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
class Integer(Simple, Enumable):
    """The base class for all integers"""

    def check(self, value):
        super().check(value)
        if self.enum is not None:
            return

        info = np.iinfo(self.numpy)
        if value < info.min or value > info.max:
            raise ValueError(f"value {value} not in range of datatype")

    def getMinMax(self):
        info = np.iinfo(self.numpy)
        min = self.minExc
        if min is None:
            min = self.minInc
        else:
            min += 1
        if min is None:
            min = info.min

        max = self.maxExc
        if max is None:
            max = self.maxInc
        else:
            max -= 1
        if max is None:
            max = info.max

        return min, max

The base class for all descriptors which can be an enumeration

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
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
class Enumable:
    """The base class for all descriptors which can be an enumeration"""
    known_classes = {"State": State,
                     "AlarmCondition": AlarmCondition}

    def __init__(self, enum=None, classId=None, **kwargs):
        if classId is not None:
            enum = self.known_classes.get(classId)
        if enum is not None:
            assert issubclass(enum, Enum)
            self.enum = enum
        super().__init__(**kwargs)

    def cast(self, other):
        if isinstance(other, self.enum):
            return other
        else:
            raise TypeError(f"{self.enum} required here")

    def toDataAndAttrs(self, data):
        h, attrs = super().toDataAndAttrs(data)
        if self.enum is not None:
            return data.value, attrs
        else:
            return h, attrs

    def toSchemaAndAttrs(self, device, state):
        schema, attrs = super().toSchemaAndAttrs(device, state)
        if self.enum is not None:
            attrs["classId"] = self.enum.__name__
            if self.enum is State:
                attrs["leafType"] = LeafType.State.value
            elif self.enum is AlarmCondition:
                attrs["leafType"] = LeafType.AlarmCondition.value
            if self.options is None:
                attrs["options"] = [val.value
                                    for val in self.enum.__members__.values()]
            else:
                attrs["options"] = [opt.value for opt in self.options]

        return schema, attrs

    def check(self, data):
        # NOTE: The State.UNKNOWN is by default allowed for a state property!
        if self.enum is State and data == State.UNKNOWN:
            return
        super().check(data)

    def toKaraboValue(self, data, strict=True):
        if not strict and not isinstance(data, self.enum):
            data = self.enum(data)
        self.check(data)
        return EnumValue(data, descriptor=self)

Atomic Descriptors

Primitive data types:

Bases: Enumable, Type

This is the type corresponding to unicode strings, which are supposed to be used for all human-readable strings, so for everything except binary data.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
class String(Enumable, Type):
    """This is the type corresponding to unicode strings, which are
    supposed to be used for all human-readable strings, so for
    everything except binary data."""
    number = 28
    numpy = np.object_  # strings better be stored as objects in numpy tables
    defaultValue = Attribute(dtype=str)

    def cast(self, other):
        if self.enum is not None:
            return super().cast(other)
        elif isinstance(other, str):
            return other
        else:
            return str(other)

    def toKaraboValue(self, data, strict=True):
        if self.enum is not None:
            return Enumable.toKaraboValue(self, data, strict)
        self.check(data)
        return StringValue(data, descriptor=self)

Bases: Vector

A VectorChar is simply some binary data in memory. The corresponding Python data type is :class:python:bytes.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
class VectorChar(Vector):
    """A VectorChar is simply some binary data in memory. The corresponding
    Python data type is :class:`python:bytes`."""
    basetype = Char
    number = 3
    numpy = np.object_

    def cast(self, other):
        if isinstance(other, bytes):
            return other
        elif isinstance(other, str):
            return bytes(other, 'utf-8')
        else:
            return bytes(other)

    def toKaraboValue(self, data, strict=True):
        self.check(data)
        return VectorCharValue(data, descriptor=self)

Bases: Type

This describes a boolean: True or False

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
822
823
824
825
826
827
828
829
830
831
832
class Bool(Type):
    """This describes a boolean: ``True`` or ``False``"""
    number = 0
    numpy = np.bool_
    defaultValue = Attribute(dtype=bool)

    def cast(self, other):
        return bool(other)

    def toKaraboValue(self, data, strict=True):
        return BoolValue(data, descriptor=self)

Bases: NumpyVector

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
835
836
837
838
839
840
841
842
class VectorBool(NumpyVector):
    basetype = Bool
    number = 1

    def cast(self, other):
        if isinstance(other, list) and other and isinstance(other[0], str):
            other = [o in ('true', 'True', '1') for o in other]
        return super().cast(other)

Bases: Vector

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
class VectorString(Vector):
    basetype = String
    number = 29
    numpy = np.object_
    # NOTE: Vectorstring should be represented as python lists
    # the np.object is simply for the table element
    defaultValue = VectorStringDefault()

    def cast(self, other):
        def check(s):
            if not isinstance(s, str):
                raise TypeError
            return s

        self.check(other)
        return [check(s) for s in other]

    def toKaraboValue(self, data, strict=True):
        self.check(data)
        return VectorStringValue(data, descriptor=self)

Bases: Simple, Type

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
class Char(Simple, Type):
    number = 2
    numpy = np.uint8  # actually not used, for convenience only

    def cast(self, other):
        try:
            return HashByte(chr(other))
        except TypeError:
            if len(str(other)) == 1:
                return HashByte(other)
            elif isinstance(other, bytes):
                o = other.decode("ascii")
                if len(o) == 1:
                    return HashByte(o)
            raise

    def toKaraboValue(self, data, strict=True):
        if isinstance(data, bytes) and len(data) == 1:
            data = data[0]
        elif isinstance(data, str) and len(data) == 1:
            data = ord(data)
        elif not (0 <= data < 256):
            raise ValueError(
                "Character must be bytes or string of length 1 "
                "or positive number < 256")
        return QuantityValue(data, descriptor=self)

Compound Descriptors

Structured types:

Bases: Type

The Image class is a helper class to provide ImageData

Along the raw pixel values it also stores useful metadata like encoding, bit depth or binning and basic transformations like flip, rotation, ROI.

This special hash Type contains an NDArray element and is constructed::

class Device(Configurable):

    data = ImageData(np.zeros(shape=(10, 10), dtype=np.uint64),
                     encoding=ENCODING.GRAY)
    image = Image(
        data=data,
        displayedName="Image")

Hence, the Image element can be initialized with an ImageData KaraboValue.

Alternatively, the Image element can be initialized by providing shape and dtype and the encoding::

image = Image(
    displayedName="Image"
    shape=(2600, 2000),
    dtype=UInt8,
    encoding=EncodingType.GRAY)

The dtype can be provided with a simple Karabo descriptor or the numpy dtype, e.g. numpy.uint8.

Source code in src/pythonKarabo/karabo/native/schema/image_data.py
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
class Image(Type):
    """The `Image` class is a helper class to provide ImageData

    Along the raw pixel values it also stores useful metadata like encoding,
    bit depth or binning and basic transformations like flip, rotation, ROI.

    This special hash `Type` contains an NDArray element and is constructed::

        class Device(Configurable):

            data = ImageData(np.zeros(shape=(10, 10), dtype=np.uint64),
                             encoding=ENCODING.GRAY)
            image = Image(
                data=data,
                displayedName="Image")

    Hence, the `Image` element can be initialized with an `ImageData`
    KaraboValue.

    Alternatively, the `Image` element can be initialized by providing `shape`
    and `dtype` and the `encoding`::

        image = Image(
            displayedName="Image"
            shape=(2600, 2000),
            dtype=UInt8,
            encoding=EncodingType.GRAY)

    The `dtype` can be provided with a simple Karabo descriptor or the numpy
    dtype, e.g. numpy.uint8.
    """

    _hashname = "HASH"
    defaultValue = Hash()

    def __init__(self, data=None, dtype=None, shape=None,
                 encoding=EncodingType.GRAY, daqDataType=DaqDataType.TRAIN,
                 **kwargs):
        if dtype is not None and shape is not None:
            if isinstance(dtype, type) and issubclass(dtype, Simple):
                dtype = dtype.numpy
            data = ImageData(numpy.zeros(shape=shape, dtype=dtype),
                             encoding=encoding)
        if data is not None and not isinstance(data, ImageData):
            raise TypeError("The `Image` element requires an `ImageData` "
                            "object for initialization, but got {} "
                            "instead".format(type(data)))

        self.__dict__.update(data.toDict())
        self.daqDataType = daqDataType
        super().__init__(accessMode=AccessMode.READONLY, **kwargs)

    def toSchemaAndAttrs(self, device, state):
        _, attrs = super().toSchemaAndAttrs(device, state)
        attrs["classId"] = "ImageData"
        attrs["displayType"] = "ImageData"
        attrs["nodeType"] = NodeType.Node
        attrs["daqDataType"] = self.daqDataType.value

        # Get the schema from the Configurable and declare default
        schema = ImageNode.getClassSchema(device, state).hash
        schema["pixels.shape", "defaultValue"] = numpy.array(
            self.shape, dtype=numpy.uint64)
        schema["pixels.type", "defaultValue"] = convert_dtype(self.dtype)
        schema["pixels.isBigEndian", "defaultValue"] = self.dtype.str[0] == ">"

        # Set the attribute defaultValues!
        schema["dims", "defaultValue"] = self.dims
        schema["dimTypes", "defaultValue"] = self.dimTypes
        schema["dimScales", "defaultValue"] = self.dimScales
        schema["encoding", "defaultValue"] = self.encoding
        schema["bitsPerPixel", "defaultValue"] = self.bitsPerPixel
        schema["roiOffsets", "defaultValue"] = self.roiOffsets
        schema["binning", "defaultValue"] = self.binning
        schema["rotation", "defaultValue"] = self.rotation
        schema["flipX", "defaultValue"] = self.flipX
        schema["flipY", "defaultValue"] = self.flipY

        # Set the attribute maxSizes!
        maxSize = numpy.uint32(len(self.shape))
        schema["pixels.shape", "maxSize"] = maxSize
        schema["dims", "maxSize"] = maxSize
        schema["dimTypes", "maxSize"] = maxSize
        schema["roiOffsets", "maxSize"] = maxSize
        schema["binning", "maxSize"] = maxSize

        return schema, attrs

    def toKaraboValue(self, data, strict=False):
        if isinstance(data, Hash) and not strict:
            if (data.empty()
                    or "pixels" not in data
                    or len(data["pixels"]) == 0):
                return NoneValue(descriptor=self)

            pixels = data["pixels"]
            dtype = dtype_from_number(data["type"])
            dtype = dtype.newbyteorder(">" if pixels["isBigEndian"] else "<")
            ar = numpy.frombuffer(pixels["data"], count=pixels["shape"].prod(),
                                  dtype=dtype)
            ar.shape = pixels["shape"]
            return ImageData(value=ar,
                             dimTypes=self.dimTypes,
                             dimScales=self.dimScales,
                             encoding=self.encoding,
                             bitsPerPixel=self.bitsPerPixel,
                             roiOffsets=self.roiOffsets,
                             binning=self.binning,
                             rotation=self.rotation,
                             flipX=self.flipX,
                             flipY=self.flipY,
                             descriptor=self)

        if isinstance(data, ImageData):
            data.descriptor = self
            return data

        if not isinstance(data, numpy.ndarray) or data.dtype != self.dtype:
            data = numpy.array(data, dtype=self.dtype)

        # If only an array was provided, we set our predefined values
        if not isinstance(data, ImageData):
            data = ImageData(value=data,
                             dimTypes=self.dimTypes,
                             dimScales=self.dimScales,
                             encoding=self.encoding,
                             bitsPerPixel=self.bitsPerPixel,
                             roiOffsets=self.roiOffsets,
                             binning=self.binning,
                             rotation=self.rotation,
                             flipX=self.flipX,
                             flipY=self.flipY,
                             descriptor=self)
        return data

    def toDataAndAttrs(self, data):
        if not isinstance(data, ImageData):
            raise TypeError("The `Image` element requires an `ImageData` "
                            "object for initialization, but got {} "
                            "instead".format(type(data)))
        attrs = {}
        if data.timestamp is not None:
            attrs = data.timestamp.toDict()

        # Set the attributes of the image data!
        h = Hash()
        h.setElement("dims", data.dims, attrs)
        h.setElement("dimTypes", data.dimTypes, attrs)
        h.setElement("dimScales", data.dimScales, attrs)
        h.setElement("encoding", data.encoding, attrs)
        h.setElement("roiOffsets", data.roiOffsets, attrs)
        h.setElement("binning", data.binning, attrs)
        h.setElement("rotation", data.rotation, attrs)
        h.setElement("bitsPerPixel", data.bitsPerPixel, attrs)
        h.setElement("flipX", data.flipX, attrs)
        h.setElement("flipY", data.flipY, attrs)

        # Finally, set the NDArray Hash!
        pixels = Hash()
        pixels.setElement("type", convert_dtype(data.dtype), attrs)
        pixels.setElement("isBigEndian", data.dtype.str[0] == ">", attrs)
        pixels.setElement("shape", data.shape, attrs)
        pixels.setElement("data", data.value.data, attrs)

        # set the `__classId` attribute to allow the C++ API to decode the
        # `pixels` Hash as an NDArray object.
        # XXX: This is a code duplication of NDArray.toDataAndAttrs
        array_attrs = {"__classId": "NDArray"}
        array_attrs.update(**attrs)
        h.setElement("pixels", pixels, array_attrs)

        # set the `__classId` attribute to allow the C++ API to decode this
        # Hash node into an ImageData Object.
        image_attrs = {"__classId": "ImageData"}
        image_attrs.update(**attrs)
        return h, image_attrs

Bases: Descriptor

Compose configurable classes into each other

with a Node you can use a Configurable object in another one as an attribute::

from karabo import Configurable, Node

class Outer(Configurable):
    inner = Node(Inner)
Source code in src/pythonKarabo/karabo/native/schema/configurable.py
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
class Node(Descriptor):
    """Compose configurable classes into each other

    with a Node you can use a Configurable object in another one as an
    attribute::

        from karabo import Configurable, Node

        class Outer(Configurable):
            inner = Node(Inner)
    """
    defaultValue = Hash()

    def __init__(self, cls, requiredAccessLevel=AccessLevel.OBSERVER,
                 **kwargs):
        self.cls = cls
        Descriptor.__init__(self, requiredAccessLevel=requiredAccessLevel,
                            **kwargs)

    def toSchemaAndAttrs(self, device, state):
        _, attrs = super().toSchemaAndAttrs(device, state)
        attrs["nodeType"] = NodeType.Node
        if self.cls.daqDataType is not None:
            attrs["daqDataType"] = self.cls.daqDataType.value
        if self.cls.displayType is not None:
            attrs["displayType"] = self.cls.displayType
        if self.cls.classId is not None:
            attrs["classId"] = self.cls.classId
        return self.cls.getClassSchema(device, state).hash, attrs

    def _initialize(self, instance, value):
        node = self.cls(value)
        instance.setValue(self, node)
        ret = node._initializers
        del node._initializers
        return ret

    def __set__(self, instance, value):
        if not isinstance(value, self.cls):
            raise RuntimeError('node "{}" must be of type "{}" not "{}"'
                               .format(self.key, self.cls.__name__,
                                       type(value).__name__))
        instance.setValue(self, value)

    def _setter(self, instance, value):
        props = ((getattr(self.cls, k), v) for k, v in value.items())
        parent = getattr(instance, self.key)
        return sum((t.checkedSet(parent, v) for t, v in props), [])

    def toDataAndAttrs(self, instance):
        r = Hash()
        for k in self.cls._allattrs:
            a = getattr(instance, k, None)
            if isSet(a):
                desc = getattr(self.cls, k)
                if isinstance(desc, Slot):
                    continue
                value, attrs = desc.toDataAndAttrs(a)
                r.setElement(k, value, attrs)
        return r, {}

    def allDescriptors(self, prefix=""):
        yield from super().allDescriptors(prefix)
        for key in self.cls._allattrs:
            yield from getattr(self.cls, key).allDescriptors(
                f"{prefix}{self.key}.")

Bases: Vector

A VectorHash is a table

The VectorHash value setting can be for example done via:

  • list of tuples: [(value1, value2), ...]
  • list of Hashes: [Hash('key1', value1, 'key2', value2), ...]

A VectorHash can be initialized via::

class RowSchema(Configurable):
    deviceId = String()
    classId = String()

table = VectorHash(
    displayedName="Table",
    rows=RowSchema)

:param row: The structure of each row. This is a :class:Configurable class.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
class VectorHash(Vector):
    """A VectorHash is a table

    The VectorHash value setting can be for example done via:

    - list of tuples: [(value1, value2), ...]
    - list of Hashes: [Hash('key1', value1, 'key2', value2), ...]

    A VectorHash can be initialized via::

        class RowSchema(Configurable):
            deviceId = String()
            classId = String()

        table = VectorHash(
            displayedName="Table",
            rows=RowSchema)

    :param row: The structure of each row. This is a :class:`Configurable`
                class.
    """
    basetype = TypeHash
    number = 31
    displayType = Attribute("Table")
    rowSchema = Attribute()

    def __init__(self, rows=None, strict=True, **kwargs):
        super().__init__(strict=strict, **kwargs)
        if rows is not None:
            self.rowSchema = rows.getClassSchema()

        if self.rowSchema is None or self.rowSchema.hash.empty():
            raise KaraboError("The table element {} does not have a valid row "
                              "schema".format(self.key))
        else:
            readonly = self.accessMode is AccessMode.READONLY
            self.rowSchema = sanitize_table_schema(self.rowSchema, readonly)

        dtype = []
        self.bindings = {}
        self.units = {}
        self.default_row = Hash()
        for k, v, a in self.rowSchema.hash.iterall():
            desc = Type.fromname[a["valueType"]](strict=False, **a)
            dtype.append((k, desc.numpy))
            self.bindings.update({k: desc})
            self.units.update({k: (a.get("unitSymbol", None),
                                   a.get("metricPrefixSymbol",
                                         MetricPrefix.NONE))})
            self.default_row.update({k: get_default_value(desc, force=True)})
        self.dtype = np.dtype(dtype)

    def cast(self, other):
        ht = TypeHash()
        return HashList(ht.cast(o) for o in other)

    def toKaraboValue(self, data, strict=True):
        timestamp = None
        if strict:
            if isinstance(data, KaraboValue):
                timestamp = data.timestamp
                data = data.value
            elif isinstance(data, list) and data:
                # NOTE: We assume a list of Hashes here!
                if isinstance(data[0], Hash):
                    data = [tuple(ele[name] for name in self.dtype.names)
                            for ele in data]
            table = np.array(data, dtype=self.dtype)
        else:
            table = np.empty(len(data), dtype=self.dtype)
            for index, row_data in enumerate(data):
                row = [self.bindings[name].toKaraboValue(
                    self.bindings[name].cast(
                        row_data[name]), strict=False).value
                       for name in self.dtype.names]
                table[index] = tuple(row)

        self.check(table)
        return TableValue(table, descriptor=self, units=self.units,
                          timestamp=timestamp)

    def toDataAndAttrs(self, value):
        if (isinstance(value, KaraboValue)
                and value.timestamp is not None):
            attrs = value.timestamp.toDict()
        else:
            attrs = {}

        data = HashList(Hash((col, row[col]) for col in self.dtype.names)
                        for row in value.value)
        return data, attrs

Special Descriptors

Advanced or regex‑based types:

Bases: String

Copy a remote device into the local's namespace

A device that controls another device may add a device node such that it has a proxy to that device while both are running. The controlled device may be configured at initialization time.

As an example, a stage controlling some motor::

class Stage(Device):
    motor = DeviceNode()

Then the motor can be acessed under self.motor. Before instantiation, a device node just looks like a string, where the id of the target device can be entered.

Source code in src/pythonKarabo/karabo/middlelayer/devicenode.py
 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
class DeviceNode(String):
    """Copy a remote device into the local's namespace

    A device that controls another device may add a device node such that
    it has a proxy to that device while both are running. The controlled
    device may be configured at initialization time.

    As an example, a stage controlling some motor::

        class Stage(Device):
            motor = DeviceNode()

    Then the motor can be acessed under ``self.motor``. Before
    instantiation, a device node just looks like a string, where the
    id of the target device can be entered.
    """
    accessMode = Attribute(AccessMode.INITONLY, dtype=AccessMode)
    assignment = Attribute(Assignment.MANDATORY, dtype=Assignment)
    displayType = Attribute("deviceNode", dtype=str)

    def __init__(self, timeout=None, **kwargs):
        # timeout for backward compatiblity
        super().__init__(**kwargs)
        if timeout is not None:
            warnings.warn(
                "The `timeout` argument for a `DeviceNode` "
                "has been deprecated.", UserWarning, stacklevel=2)

    def toDataAndAttrs(self, value):
        if not isinstance(value, str):
            # If we have a proxy connection, the value is the `Proxy`,
            # otherwise we have a `StringValue`
            value = value.__meta_deviceId
        data, attrs = super().toDataAndAttrs(value)
        return data, attrs

    def checkedInit(self, instance, value=None):
        """Device Nodes cannot have an empty string
        """
        if not value:
            if self.defaultValue:
                return self._initialize(instance, self.defaultValue)
            raise KaraboError('Assignment is mandatory for "{}"'.format(
                self.key))
        return self._initialize(instance, value)

    async def finalize_init(self, instance):
        """Sets a real proxy to the MetaProxy"""
        meta_proxy = instance._getValue(self.key)
        assert isinstance(meta_proxy, MetaProxy)
        assert meta_proxy.proxy is None, "proxy already initialized"

        value = meta_proxy.deviceId
        root = instance.get_root()
        proxy = await root._sigslot.exitStack.enter_async_context(
            getDevice(value))
        proxy.__meta_deviceId = value
        meta_proxy.proxy = proxy

    def _initialize(self, instance, value):
        # This should not happen as we are mandatory, but we never know
        if not isSet(value):
            instance.__dict__[self.key] = None
            return []
        meta_proxy = MetaProxy(value)
        instance.__dict__[self.key] = meta_proxy
        # return an half instantiated deviceNode
        return []

    def __get__(self, instance, owner):
        if instance is None:
            return self

        value = instance._getValue(self.key)
        if isinstance(value, MetaProxy):
            value = value.value
        return value

checkedInit(instance, value=None)

Device Nodes cannot have an empty string

Source code in src/pythonKarabo/karabo/middlelayer/devicenode.py
72
73
74
75
76
77
78
79
80
def checkedInit(self, instance, value=None):
    """Device Nodes cannot have an empty string
    """
    if not value:
        if self.defaultValue:
            return self._initialize(instance, self.defaultValue)
        raise KaraboError('Assignment is mandatory for "{}"'.format(
            self.key))
    return self._initialize(instance, value)

finalize_init(instance) async

Sets a real proxy to the MetaProxy

Source code in src/pythonKarabo/karabo/middlelayer/devicenode.py
82
83
84
85
86
87
88
89
90
91
92
93
async def finalize_init(self, instance):
    """Sets a real proxy to the MetaProxy"""
    meta_proxy = instance._getValue(self.key)
    assert isinstance(meta_proxy, MetaProxy)
    assert meta_proxy.proxy is None, "proxy already initialized"

    value = meta_proxy.deviceId
    root = instance.get_root()
    proxy = await root._sigslot.exitStack.enter_async_context(
        getDevice(value))
    proxy.__meta_deviceId = value
    meta_proxy.proxy = proxy

Bases: VectorString

The VectorRegexString descriptor is used as follows::

data = VectorRegexString(regex="0|1")

A corresponding displayType is automatically set. The descriptor validates value input on set.

The regex is validated with each single value of the vector (list).

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
class VectorRegexString(VectorString):
    """The `VectorRegexString` descriptor is used as follows::

            data = VectorRegexString(regex="0|1")

        A corresponding displayType is automatically set.
        The descriptor validates value input on set.

        The regex is validated with each single value of the
        vector (list).
    """
    classId = "VectorRegexString"
    regex = Attribute("")

    def __init__(self, flags=0, **kwargs):
        super().__init__(**kwargs)
        self._pattern = re.compile(self.regex, flags)
        self.displayType = "VectorRegexString"

    def check(self, data):
        super().check(data)
        for index, string in enumerate(data):
            if not self._pattern.match(string):
                raise KaraboError(f"Value {string} on index {index} does not "
                                  f"comply with regex pattern {self.regex}!")

Bases: String

The RegexString descriptor is used as follows::

data = RegexString(regex="0|1")

A corresponding displayType is automatically set. The descriptor validates value input on set.

Source code in src/pythonKarabo/karabo/native/schema/descriptors.py
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
class RegexString(String):
    """The `RegexString` descriptor is used as follows::

            data = RegexString(regex="0|1")

        A corresponding displayType is automatically set.
        The descriptor validates value input on set.
    """
    classId = "RegexString"
    regex = Attribute("")

    def __init__(self, flags=0, **kwargs):
        super().__init__(**kwargs)
        self._pattern = re.compile(self.regex, flags)
        self.displayType = "RegexString"

    def check(self, data):
        if not self._pattern.match(data):
            raise KaraboError(f"Value {data} does not comply with regex "
                              f"pattern {self.regex}!")
        super().check(data)

Karabo Data Types

Values and quantifiable types:

This is the base class for all Karabo values.

All attributes of a Karabo device contain objects of these types, as they are the only ones we know how to transport over the network.

A :class:KaraboValue contains attributes that describe the value further:

.. attribute:: descriptor

This contains all the details of the datatype we have. It is an object of :class:~karabo.middlelayer.Descriptor, look there for what it contains. The descriptor is only available when accessing the device attributes directly. Values calulated from a :class:KaraboValue lose their descriptor, as it does not apply to them anymore.

.. attribute:: timestamp

This is the time a value has been acquired by the underlying hardware devices. It is an object of :class:~karabo.middlelayer.Timestamp.

When doing operations on :class:KaraboValue, the result takes the newest timestamp of the values operated on. So for the example 2 * x + y, the newest timestamp of x and y is taken, if both have a timestamp.

.. attribute:: tid

This is the macro id number attached to the timestamp.

.. attribute:: value

This is the bare value, without any special Karabo things or units attached.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
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
class KaraboValue:
    """This is the base class for all Karabo values.

    All attributes of a Karabo device contain objects of these types, as they
    are the only ones we know how to transport over the network.

    A :class:`KaraboValue` contains attributes that describe the value
    further:

    .. attribute:: descriptor

      This contains all the details of the datatype we have. It
      is an object of :class:`~karabo.middlelayer.Descriptor`, look there for
      what it contains.
      The descriptor is only available when accessing the device attributes
      directly. Values calulated from a :class:`KaraboValue` lose their
      descriptor, as it does not apply to them anymore.

    .. attribute:: timestamp

      This is the time a value has been acquired by the underlying hardware
      devices. It is an object of :class:`~karabo.middlelayer.Timestamp`.

      When doing operations on :class:`KaraboValue`, the result takes the
      newest timestamp of the values operated on. So for the example
      ``2 * x + y``, the newest timestamp of ``x`` and ``y`` is taken,
      if both have a timestamp.

    .. attribute:: tid

      This is the macro id number attached to the timestamp.

    .. attribute:: value

      This is the bare value, without any special Karabo things or units
      attached.
    """
    # Karabo stores the device this value belongs to here to do its magic
    _parent = Weak()

    def __init__(self, value, *args, timestamp=None, descriptor=None,
                 **kwargs):
        super().__init__(*args, **kwargs)
        if isinstance(value, KaraboValue) and timestamp is None:
            self.timestamp = value.timestamp
        else:
            self.timestamp = timestamp
        self.descriptor = descriptor

    def getdoc(self):
        """This is called by iPython/iKarabo to get the documentation

        See :func:`IPython.core.oinspect.getdoc`
        """
        return self.descriptor.description

    @property
    def has_tid(self):
        """Property to indicate whether our KaraboValue has a valid trainId
        """
        return self.timestamp.tid > 0

    @property
    def tid(self):
        """Property to directly provide the trainId of the KaraboValue
        """
        return self.timestamp.tid

    def __iter__(self):
        for a in super().__iter__():
            y = wrap(a)
            y.timestamp = self.timestamp
            yield y

    def _repr_html_generator_(self):
        yield escape(str(self))

    def _repr_html_(self):
        return "".join(self._repr_html_generator_())

has_tid property

Property to indicate whether our KaraboValue has a valid trainId

tid property

Property to directly provide the trainId of the KaraboValue

getdoc()

This is called by iPython/iKarabo to get the documentation

See :func:IPython.core.oinspect.getdoc

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
166
167
168
169
170
171
def getdoc(self):
    """This is called by iPython/iKarabo to get the documentation

    See :func:`IPython.core.oinspect.getdoc`
    """
    return self.descriptor.description

Test whether value actually has a value

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
256
257
258
def isSet(value):
    """Test whether *value* actually has a value"""
    return not (value is None or isinstance(value, NoneValue))

Bases: KaraboValue, Quantity

The base class for all Karabo numerical values, including vectors.

It has a unit (by virtue of inheriting a :ref:pint <pint:tutorial> Quantity). Vectors are represented by numpy arrays.

In addition to the timestamp processing common to any :class:KaraboValue, a :class:QuantityValue also has a unit.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
@wrap_methods
class QuantityValue(KaraboValue, Quantity):
    """The base class for all Karabo numerical values, including vectors.

    It has a unit (by virtue of inheriting a :ref:`pint <pint:tutorial>`
    Quantity). Vectors are represented by numpy arrays.

    In addition to the timestamp processing common to any :class:`KaraboValue`,
    a :class:`QuantityValue` also has a unit.
    """

    def __new__(cls, value, unit=None, metricPrefix=MetricPrefix.NONE, *,
                descriptor=None, timestamp=None):
        # weirdly, Pint uses __new__. Dunno why, but we need to follow.
        if isinstance(unit, Unit):
            if unit is Unit.NOT_ASSIGNED:
                unit = ""
            else:
                unit = unit.name
            if metricPrefix is MetricPrefix.NONE:
                prefix = ""
            else:
                prefix = metricPrefix.name
            self = super().__new__(cls, value, (prefix + unit).lower())
        else:
            self = super().__new__(cls, value, unit)
            if (unit is None and descriptor is not None and
                    not self.dimensionality and
                    not isinstance(value, QuantityValue)):
                # if pint didn't find a dimension in value,
                # get it from the descriptor
                self = super().__new__(cls, value, descriptor.units)
        return self

    def __init__(self, value, unit=None, metricPrefix=None, **kwargs):
        super().__init__(value, **kwargs)
        if (self.descriptor is not None and
                self.descriptor.dimensionality != self.dimensionality):
            raise pint.DimensionalityError(self.descriptor.dimensionality,
                                           self.dimensionality)

    @property
    def T(self):
        ret = super().T
        ret.timestamp = self.timestamp
        return ret

    @property
    def real(self):
        ret = super().real
        ret.timestamp = self.timestamp
        return ret

    @property
    def imag(self):
        ret = super().imag
        ret.timestamp = self.timestamp
        return ret

    @property
    def value(self):
        return self.magnitude

    def __index__(self):
        if self.dimensionality:
            raise pint.DimensionalityError("index must be dimensionless")
        return self.magnitude.__index__()

    def __getattr__(self, attr):
        ret = super().__getattr__(attr)
        if callable(ret):
            return wrap_function(ret, self.timestamp)
        return ret

    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        ret = super().__array_ufunc__(ufunc, method, *inputs, **kwargs)
        if ret is NotImplemented:
            raise NotImplementedError(
                f"Cannot compute the ufunc {ufunc.__name__}. It is "
                f"currently not supported by pint.")
        if not isinstance(ret, QuantityValue):
            return ret
        ret.timestamp = newest_timestamp(inputs)
        return ret

    def __array_wrap__(self, obj, context=None):
        ret = super().__array_wrap__(obj, context)
        if not isinstance(ret, QuantityValue):
            return ret
        _, objs, _ = context
        ret.timestamp = newest_timestamp(objs)
        return ret

    def __iadd__(self, other):
        if not isinstance(other, QuantityValue):
            self.timestamp = None
        return super().__iadd__(other)

    def __isub__(self, other):
        if not isinstance(other, QuantityValue):
            self.timestamp = None
        return super().__isub__(other)

    def __imul__(self, other):
        if not isinstance(other, QuantityValue):
            self.timestamp = None
        return super().__imul__(other)

    def __itruediv__(self, other):
        if not isinstance(other, QuantityValue):
            self.timestamp = None
        return super().__itruediv__(other)

    def __ifloordiv__(self, other):
        if not isinstance(other, QuantityValue):
            self.timestamp = None
        return super().__ifloordiv__(other)

    def _format(self, value, fmt=""):
        absolute = getattr(self.descriptor, "absoluteError", None)
        relative = getattr(self.descriptor, "relativeError", None)

        if relative is None:
            try:
                relative = numpy.finfo(value.dtype).resolution
            except (AttributeError, ValueError):
                pass

        if absolute is not None and self.value != 0:
            # TODO: this branch is not covered by tests
            err = abs(absolute / self.value)
            if relative is not None:
                err = max(err, relative)
        elif relative is not None:
            err = relative
        else:
            return f"{{:~{fmt}}}".format(value)

        err = 1 - int(numpy.log10(err))
        if err > 0:
            if isinstance(value.value, numpy.ndarray):
                # XXX: [1., 2.] will be printed as '[1.0 2.0]'
                _formatter = {'float_kind': '{{:.{}{}}}'.format(
                    err, fmt).format}
                formatted_value = numpy.array2string(value.value,
                                                     formatter=_formatter)
                ret = f"{formatted_value} {value.units:~}"
            else:
                # old behaviour for floats
                ret = f"{{:.{err}~{fmt}}}".format(1.0 * value)
        else:
            # XXX: the following string always return [0], regardless of the
            #  size of the initial array
            # TODO: this branch is not covered by tests
            ret = f"{{:~{fmt}}}".format(0 * value)
        return ret

    def _repr_pretty_(self, p, cycle):
        try:
            if self.descriptor.displayType.startswith("bin|"):
                fields = self.descriptor.displayType[4:].split(",")
                fields = (field.split(":") for field in fields)
                fields = ((int(bit), name) for bit, name in fields)
                first = True
                p.text("{ ")
                for bit, name in fields:
                    if self.value & (1 << bit):
                        if not first:
                            p.breakable()
                            p.text("| ")
                        first = False
                        p.text(name)
                p.text(" }")
                return
            formats = dict(hex="0x{:x}", oct="0o{:o}", bin="0b{:b}")
            p.text(formats[self.descriptor.displayType].format(self.value))
            return
        except (AttributeError, KeyError):
            # Attribute Errors if displayType is None
            # KeyError if we do not have the format
            pass
        p.text(self._format(self))

    _fmt_pattern = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)")

    def _repr_html_generator_(self):
        try:
            if self.descriptor.displayType.startswith("bin|"):
                fields = self.descriptor.displayType[4:].split(",")
                fields = (field.split(":") for field in fields)
                fields = ((int(bit), name) for bit, name in fields)
                res = "<br/>".join(escape(name) for bit, name in fields
                                   if self.value & (1 << bit))
                yield res
                return
            formats = dict(hex="0x{:x}", oct="0o{:o}", bin="0b{:b}")
            yield formats[self.descriptor.displayType].format(self.value)
            return
        except (AttributeError, KeyError):
            # Attribute Errors if displayType is None
            # KeyError if we do not have the format
            pass
        fmt = self._format(self, "H")
        yield self._fmt_pattern.sub(r"\1 × 10<sup>\2\3</sup>", fmt)

    def __str__(self):
        try:
            if self.descriptor.displayType.startswith("bin|"):
                fields = self.descriptor.displayType[4:].split(",")
                fields = (field.split(":") for field in fields)
                fields = ((int(bit), name) for bit, name in fields)
                res = "|".join(name for bit, name in fields
                               if self.value & (1 << bit))
                return f"{{{res}}}"
            formats = dict(hex="0x{:x}", oct="0o{:o}", bin="0b{:b}")
            return formats[self.descriptor.displayType].format(self.value)
        except (AttributeError, KeyError):
            # Attribute Errors if displayType is None
            # KeyError if we do not have the format
            pass
        return self._format(self)

Bases: StringlikeValue, str

A StringValue is a Python :class:str.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
343
344
345
346
347
348
349
@wrap_methods
class StringValue(StringlikeValue, str):
    """A StringValue is a Python :class:`str`."""

    @property
    def value(self):
        return str(self)

Bases: KaraboValue, list

A Karabo VectorStringValue corresponds to a Python list.

We check that only strings are entered

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
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
@wrap_methods
class VectorStringValue(KaraboValue, list):
    """A Karabo VectorStringValue corresponds to a Python list.

    We check that only strings are entered
    """

    def __init__(self, value=None, **kwargs):
        super().__init__(value, **kwargs)
        if value is not None:
            self.extend(value)
            for s in self:
                if not isinstance(s, str):
                    raise TypeError(
                        "Vector of strings can only contain strings")

    def __repr__(self):
        return "VectorString" + super().__repr__()

    def _repr_html_generator_(self):
        yield "<br />".join(escape(s) for s in self)

    def _repr_pretty_(self, p, cycle):
        with p.group(1, "[", "]"):
            if self:
                p.text(self.value[0])
            for s in self.value[1:]:
                p.text(",")
                p.breakable()
                p.text(s)

    @property
    def value(self):
        return list(self)

Bases: KaraboValue

This wraps numpy structured arrays. Pint cannot deal with them.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
class TableValue(KaraboValue):
    """This wraps numpy structured arrays. Pint cannot deal with them."""

    def __init__(self, value, units, **kwargs):
        super().__init__(value, **kwargs)
        self.value = value
        self.units = units

    def __getitem__(self, item):
        val = self.value[item]
        units = self.units
        if isinstance(item, str):
            units = units[item]
        elif self.value.dtype.fields is not None:
            return TableValue(val, units, timestamp=self.timestamp)

        if isinstance(val, numpy.ndarray) and (
                val.base is self.value
                or val.base is self.value.base) and (
                val.dtype.char == "O"):
            return TableValue(val, units, timestamp=self.timestamp)

        ret = wrap(val)
        ret.timestamp = self.timestamp
        if isinstance(ret, QuantityValue):
            unit = Unit(units[0]) if units[0] is not None else None
            # NOTE: metricPrefix has a default NONE
            metricPrefix = MetricPrefix(units[1])
            return QuantityValue(ret.value, unit=unit,
                                 metricPrefix=metricPrefix,
                                 timestamp=self.timestamp)
        return ret

    def __setitem__(self, item, value):
        if not isinstance(item, slice):
            item = slice(item, item + 1)
        if isinstance(value, Hash):
            value = [tuple(value[key] for key in self.value.dtype.names)]
        converted = numpy.array(value, dtype=self.value.dtype)
        if converted.shape == ():
            converted.shape = (1,)
        start, stop, stride = item.indices(len(self.value))
        if item.step is not None or len(converted) == (stop - start) // stride:
            newvalue = self.value.copy()
            newvalue[item] = converted
        else:
            newvalue = numpy.concatenate(
                (self.value[:start], converted, self.value[stop:]))
        self.descriptor.__set__(self._parent, newvalue)

    def __delitem__(self, item):
        self[item] = []

    def insert(self, index, value):
        """Insert a `value` into the TableValue at `index`"""
        self[index:index] = value

    def extend(self, value):
        """Extend the `TableValue` by a `value`"""
        self[len(self.value):] = value

    def append(self, value):
        """Append `value` to the `TableValue`"""
        self.extend(value)

    def pop(self, index=-1):
        """Pops a single TableValue from the table

        NOTE: This method can only be used with a descriptor!
        """
        v = self[index]
        self.value = numpy.delete(self.value, index)
        self.descriptor.__set__(self._parent, self.value)
        return v

    def columnIndex(self, field):
        return self.value.dtype.names.index(field)

    def where(self, field, value, op=operator.eq):
        """Return indexes of the table where a condition for a column is met

        :param field: The field of the row schema, e.g. string key
        :param value: The value for the condition
        :param op: Operator condition

        :returns: list of indexes
        """
        if field not in self.value.dtype.names:
            raise AttributeError(f"Specified field attribute {field} not"
                                 " present in column schema.")
        return numpy.where(op(self.value[field], value))[0]

    def where_value(self, field, value, op=operator.eq):
        """Filter the table with a condition for a column

        :param field: The field of the row schema, e.g. string key
        :param value: The value for the condition
        :param op: Instance of operator condition

        :returns: Filtered TableValue
        """
        indexes = self.where(field, value, op)
        value = self.value[indexes]
        return TableValue(value, self.units, timestamp=self.timestamp)

    def default_row(self):
        """Return a row Hash with default values of the `TableValue`

        Added in 2.16.X: This method safely returns a quick deepcopy!
        """
        return self.descriptor.default_row.deepcopy()

    def clear(self):
        """Clear the table element with a single message"""
        self.value = numpy.array([], dtype=self.value.dtype)
        self.descriptor.__set__(self._parent, self.value)

    def to_hashlist(self):
        """Convert the TableValue as `Hashlist` but lose the attributes"""
        ret = HashList(Hash({key: value for key, value in
                             zip(self.value.dtype.names, row)})
                       for row in self.value)
        return ret

    def __len__(self):
        return len(self.value)

    def __getattr__(self, attr):
        return getattr(self.value, attr)

    def __iter__(self):
        for row in self.value:
            yield TableValue(row, self.units, timestamp=self.timestamp)

    def iter_hashes(self):
        """Iterate over the table by providing the Hash elements

        This method does NOT provide a copy!

        Note: Added in Karabo 2.16.X
        """
        names = self.value.dtype.names
        for row in self.value:
            yield Hash({key: value for key, value in
                        zip(names, row)})

    def __repr__(self):
        table = [{key: value for key, value in
                 zip(self.value.dtype.names, row)}
                 for row in self.value]
        return tabulate.tabulate(table, headers="keys", tablefmt="grid")

    def _repr_html_generator_(self):
        yield "<table><tr>"
        for name in self.value.dtype.names:
            yield f"<th>{name}</th>"
        for row in self.value:
            yield "</tr><tr>"
            for col in row:
                yield "<td>"
                yield from col._repr_html_generator_()
                yield "</td>"
        yield "</tr></table>"

append(value)

Append value to the TableValue

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
553
554
555
def append(self, value):
    """Append `value` to the `TableValue`"""
    self.extend(value)

clear()

Clear the table element with a single message

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
604
605
606
607
def clear(self):
    """Clear the table element with a single message"""
    self.value = numpy.array([], dtype=self.value.dtype)
    self.descriptor.__set__(self._parent, self.value)

default_row()

Return a row Hash with default values of the TableValue

Added in 2.16.X: This method safely returns a quick deepcopy!

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
597
598
599
600
601
602
def default_row(self):
    """Return a row Hash with default values of the `TableValue`

    Added in 2.16.X: This method safely returns a quick deepcopy!
    """
    return self.descriptor.default_row.deepcopy()

extend(value)

Extend the TableValue by a value

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
549
550
551
def extend(self, value):
    """Extend the `TableValue` by a `value`"""
    self[len(self.value):] = value

insert(index, value)

Insert a value into the TableValue at index

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
545
546
547
def insert(self, index, value):
    """Insert a `value` into the TableValue at `index`"""
    self[index:index] = value

iter_hashes()

Iterate over the table by providing the Hash elements

This method does NOT provide a copy!

Note: Added in Karabo 2.16.X

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
626
627
628
629
630
631
632
633
634
635
636
def iter_hashes(self):
    """Iterate over the table by providing the Hash elements

    This method does NOT provide a copy!

    Note: Added in Karabo 2.16.X
    """
    names = self.value.dtype.names
    for row in self.value:
        yield Hash({key: value for key, value in
                    zip(names, row)})

pop(index=-1)

Pops a single TableValue from the table

NOTE: This method can only be used with a descriptor!

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
557
558
559
560
561
562
563
564
565
def pop(self, index=-1):
    """Pops a single TableValue from the table

    NOTE: This method can only be used with a descriptor!
    """
    v = self[index]
    self.value = numpy.delete(self.value, index)
    self.descriptor.__set__(self._parent, self.value)
    return v

to_hashlist()

Convert the TableValue as Hashlist but lose the attributes

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
609
610
611
612
613
614
def to_hashlist(self):
    """Convert the TableValue as `Hashlist` but lose the attributes"""
    ret = HashList(Hash({key: value for key, value in
                         zip(self.value.dtype.names, row)})
                   for row in self.value)
    return ret

where(field, value, op=operator.eq)

Return indexes of the table where a condition for a column is met

:param field: The field of the row schema, e.g. string key :param value: The value for the condition :param op: Operator condition

:returns: list of indexes

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
570
571
572
573
574
575
576
577
578
579
580
581
582
def where(self, field, value, op=operator.eq):
    """Return indexes of the table where a condition for a column is met

    :param field: The field of the row schema, e.g. string key
    :param value: The value for the condition
    :param op: Operator condition

    :returns: list of indexes
    """
    if field not in self.value.dtype.names:
        raise AttributeError(f"Specified field attribute {field} not"
                             " present in column schema.")
    return numpy.where(op(self.value[field], value))[0]

where_value(field, value, op=operator.eq)

Filter the table with a condition for a column

:param field: The field of the row schema, e.g. string key :param value: The value for the condition :param op: Instance of operator condition

:returns: Filtered TableValue

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
584
585
586
587
588
589
590
591
592
593
594
595
def where_value(self, field, value, op=operator.eq):
    """Filter the table with a condition for a column

    :param field: The field of the row schema, e.g. string key
    :param value: The value for the condition
    :param op: Instance of operator condition

    :returns: Filtered TableValue
    """
    indexes = self.where(field, value, op)
    value = self.value[indexes]
    return TableValue(value, self.units, timestamp=self.timestamp)

Bases: KaraboValue

This contains an enum.

We can define enums in the expected parameters. This contains a value of them. Unfortunately, it is impossible to use the is operator as with bare enums, one has to use == instead.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
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
@wrap_methods
class EnumValue(KaraboValue):
    """This contains an enum.

    We can define enums in the expected parameters. This contains a value of
    them. Unfortunately, it is impossible to use the ``is`` operator as with
    bare enums, one has to use ``==`` instead. """

    def __init__(self, value, *, descriptor=None, **kwargs):
        super().__init__(value, descriptor=descriptor, **kwargs)
        if isinstance(value, EnumValue):
            if descriptor is None:
                descriptor = value.descriptor
            value = value.enum
        if descriptor is not None and not isinstance(value, descriptor.enum):
            raise TypeError('value "{}" is not element of enum "{}"'.
                            format(value, descriptor.enum))
        if not isinstance(value, Enum):
            raise TypeError(f'value "{value}" must be an Enum')
        self.enum = value

    def __eq__(self, other):
        if isinstance(other, EnumValue):
            return self.enum is other.enum
        else:
            return self.enum is other

    def __str__(self):
        return str(self.enum)

    def __repr__(self):
        return repr(self.enum)

    def _repr_pretty_(self, p, cycle):
        p.text(f"<{self}>")

    def _repr_html_generator_(self):
        yield f"<i>{self}</i>"

    def __hash__(self):
        return hash(self.enum)

    @property
    def value(self):
        return self.enum.value

Bases: _Singleton

This contains bools.

Objects of this class behave effectively like normal bools, just with a timestamp and a descriptor added.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
@wrap_methods
class BoolValue(_Singleton):
    """This contains bools.

    Objects of this class behave effectively like normal bools, just
    with a timestamp and a descriptor added.
    """
    _hashType = HashType.Bool

    # We cannot inherit from bool, so we need a brand-new class

    def __init__(self, value, **kwargs):
        super().__init__(value, **kwargs)
        self.value = bool(value)

    def __int__(self):
        return int(self.value)

    def __float__(self):
        return float(self.value)

    def __eq__(self, other):
        return self.value == bool(other)

Bases: KaraboValue

The Karabo ImageData is supposed to provide an encapsulated NDArray

This KaraboValue can estimate from the input array the associated attributes of the ImageData, such as binning, encoding, etc.

Every attribute can be provided as well on initialization of the object.

:param binning: The binning of the image, e.g. [0, 0] :param encoding: The encoding of the image, e.g. EncodingType.GRAY (enum) :param rotation: The rotation of the image, either 0, 90, 180 or 270 :param roiOffsets: The roiOffset, e.g. [0, 0] :param dimScales: Description of the dim scales :param dimTypes: The dimension types :param bitsPerPixel: The bits per pixel :param flipX: Image horizontal flip, either True or False :param flipY: Image vertical flip, either True or False

:type binning: array with dtype uint64 :type encoding: integer with dtype int32 :type rotation: integer with dtype int32 :type roiOffsets: array with dtype uint64 :type dimScales: str :type dimTypes: array with dtype int32 :type flipX: bool :type flipY: bool

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
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
485
486
487
488
489
class ImageData(KaraboValue):
    """The Karabo ImageData is supposed to provide an encapsulated NDArray

    This KaraboValue can estimate from the input array the associated
    attributes of the `ImageData`, such as binning, encoding, etc.

    Every attribute can be provided as well on initialization of the object.

    :param binning: The binning of the image, e.g. [0, 0]
    :param encoding: The encoding of the image, e.g. EncodingType.GRAY (enum)
    :param rotation: The rotation of the image, either 0, 90, 180 or 270
    :param roiOffsets: The roiOffset, e.g. [0, 0]
    :param dimScales: Description of the dim scales
    :param dimTypes: The dimension types
    :param bitsPerPixel: The bits per pixel
    :param flipX: Image horizontal flip, either `True` or `False`
    :param flipY: Image vertical flip, either `True` or `False`

    :type binning: array with dtype uint64
    :type encoding: integer with dtype int32
    :type rotation: integer with dtype int32
    :type roiOffsets: array with dtype uint64
    :type dimScales: str
    :type dimTypes: array with dtype int32
    :type flipX: bool
    :type flipY: bool
    """

    def __init__(self, value, *args, binning=None, encoding=None,
                 rotation=None, roiOffsets=None, dimScales=None, dimTypes=None,
                 bitsPerPixel=None, flipX=False, flipY=False, **kwargs):
        super().__init__(value, *args, **kwargs)
        self.value = value
        self.dtype = value.dtype

        dims = numpy.array(value.shape, dtype=numpy.uint64)
        self.shape = dims
        self.dims = dims

        # NOTE: If the encoding is not provided, try to guess it!
        if encoding is None:
            if len(dims) == 2:
                encoding = EncodingType.GRAY
            elif len(dims) == 3:
                if dims[2] == 1:
                    encoding = EncodingType.GRAY
                elif dims[2] == 3:
                    encoding = EncodingType.RGB
                elif dims[2] == 4:
                    encoding = EncodingType.RGBA
                else:
                    encoding = EncodingType.UNDEFINED
            else:
                encoding = EncodingType.UNDEFINED

        if isinstance(encoding, Enum):
            encoding = encoding.value

        self.encoding = numpy.int32(encoding)

        self.dimScales = dimScales if dimScales else ""
        dimTypes = [] if dimTypes is None else dimTypes
        self.dimTypes = numpy.array(dimTypes, dtype=numpy.int32)
        binning = [1] * len(dims) if binning is None else binning
        self.binning = numpy.array(binning, dtype=numpy.uint64)
        roiOffsets = [0] * len(dims) if roiOffsets is None else roiOffsets
        self.roiOffsets = numpy.array(roiOffsets, dtype=numpy.uint64)
        rotation = 0 if rotation is None else rotation
        self.rotation = numpy.int32(rotation)

        # NOTE: Set the bits per pixel depending on the shape if `None`
        if bitsPerPixel is None:
            itemsize = value.itemsize
            if len(dims) == 2:
                bitsPerPixel = 8 * itemsize
            elif len(dims) == 3:
                bitsPerPixel = 8 * itemsize * self.shape[2]
            else:
                bitsPerPixel = 8

        self.bitsPerPixel = numpy.int32(bitsPerPixel)
        self.flipX = flipX
        self.flipY = flipY

    def toDict(self):
        """Provide the `attributes` of the `ImageData` in a dictionary form"""
        data = {
            'dtype': self.dtype,
            'shape': self.shape,
            'encoding': self.encoding,
            'dimScales': self.dimScales,
            'dimTypes': self.dimTypes,
            'dims': self.dims,
            'binning': self.binning,
            'roiOffsets': self.roiOffsets,
            'rotation': self.rotation,
            'bitsPerPixel': self.bitsPerPixel,
            'flipX': self.flipX,
            'flipY': self.flipY
        }

        return data

toDict()

Provide the attributes of the ImageData in a dictionary form

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
def toDict(self):
    """Provide the `attributes` of the `ImageData` in a dictionary form"""
    data = {
        'dtype': self.dtype,
        'shape': self.shape,
        'encoding': self.encoding,
        'dimScales': self.dimScales,
        'dimTypes': self.dimTypes,
        'dims': self.dims,
        'binning': self.binning,
        'roiOffsets': self.roiOffsets,
        'rotation': self.rotation,
        'bitsPerPixel': self.bitsPerPixel,
        'flipX': self.flipX,
        'flipY': self.flipY
    }

    return data

Bases: _Singleton

This represents a value which is not set.

This is mostly the Karabo equivalent of None.

Source code in src/pythonKarabo/karabo/native/schema/basetypes.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
class NoneValue(_Singleton):
    """This represents a value which is not set.

    This is mostly the Karabo equivalent of `None`.
    """
    _hashType = HashType.None_
    value = None

    def __init__(self, value=None, **kwargs):
        assert value is None or isinstance(value, NoneValue)
        super().__init__(value, **kwargs)

    def __eq__(self, other):
        return other is None or isinstance(other, NoneValue)

This is a time stamp

:param date: is either another timestamp (then we copy), None (we return now), a float (from time.time()), an integer (raw timestamp) or a string that will be parsed with :mod:dateutil

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
 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
@total_ordering
class Timestamp:
    """This is a time stamp

    :param date: is either another timestamp (then we copy), None
                 (we return now), a float (from time.time()), an integer
                 (raw timestamp) or a string that will be parsed
                 with :mod:`dateutil`
    """

    # Reduce the memory needed for this _very_ common object
    __slots__ = ['time', '_tid']

    def __init__(self, date=None):
        self._tid = numpy.uint64(0)
        if date is None:
            self.time = int(time.time() * RESOLUTION)
        elif isinstance(date, Timestamp):
            self.time = date.time
        elif isinstance(date, float):
            self.time = int(date * RESOLUTION)
        elif isinstance(date, int):
            self.time = date
        else:
            d = dateutil.parser.parse(date)
            if d.tzinfo is None:
                d = d.replace(tzinfo=dateutil.tz.tzlocal())
            self.time = int(d.timestamp() * RESOLUTION)

    @classmethod
    def fromHashAttributes(cls, attrs):
        if 'sec' not in attrs:
            return None
        ret = cls.__new__(cls)
        # Avoid overflows, cast back to integer
        ret.time = int(attrs['frac']) + int(attrs['sec']) * RESOLUTION
        ret.tid = attrs['tid']
        return ret

    def toHashAttributes(self, hash_):
        time_properties = self.toDict()
        for entry in hash_:
            for k, v in time_properties.items():
                hash_.setAttribute(entry, k, v)

    @property
    def time_frac(self):
        """The fractional seconds of the timestamp in attoseconds
        """
        return numpy.uint64(self.time % RESOLUTION)

    @property
    def time_sec(self):
        """The seconds of the timestamp from epoch
        """
        return numpy.uint64(self.time // RESOLUTION)

    @property
    def tid(self):
        """The train Id associated with this Timestamp
        """
        return self._tid

    @tid.setter
    def tid(self, value):
        self._tid = numpy.uint64(value)

    def toDict(self):
        return {"frac": self.time_frac,
                "sec": self.time_sec,
                "tid": self.tid}

    def toTimestamp(self):
        """Return the time as seconds since 1970-01-01 00:00 UTC"""
        return self.time / RESOLUTION

    def toLocal(self, sep="T", timespec="auto"):
        """
        Return the time as an ISO 8601 string in the local timezone

        :param sep: unicode character to separate date and time, default is 'T'
        :param timespec: specifies the number of additional components
                         of the time to include, defaults to 'auto'.
                         Further options are 'hours', 'minutes', 'seconds',
                         'milliseconds' and 'microseconds'
        """
        return datetime.fromtimestamp(self.toTimestamp()).isoformat(
            sep, timespec=timespec)

    def __eq__(self, other):
        if not isinstance(other, Timestamp):
            return False
        return self.time == other.time

    def __lt__(self, other):
        if not isinstance(other, Timestamp):
            return NotImplemented
        return self.time < other.time

    def __str__(self):
        """See toLocal()"""
        return self.toLocal()

    def __repr__(self):
        """Return the time as an ISO 8601 string in UTC"""
        ts = datetime.fromtimestamp(
            self.toTimestamp(), tz=timezone.utc).replace(tzinfo=None)
        return ts.isoformat() + " UTC"

    def __add__(self, other):
        """Add operator for the time in seconds since 1970-01-01 00:00 UTC"""
        if isinstance(other, Timestamp):
            return self.toTimestamp() + other.toTimestamp()
        elif isinstance(other, (float, int)):
            return self.toTimestamp() + other

        return NotImplemented

    def __sub__(self, other):
        """Sub operator for the time in seconds since 1970-01-01 00:00 UTC"""
        if isinstance(other, Timestamp):
            return self.toTimestamp() - other.toTimestamp()
        elif isinstance(other, (float, int)):
            return self.toTimestamp() - other

        return NotImplemented

tid property writable

The train Id associated with this Timestamp

time_frac property

The fractional seconds of the timestamp in attoseconds

time_sec property

The seconds of the timestamp from epoch

__add__(other)

Add operator for the time in seconds since 1970-01-01 00:00 UTC

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
139
140
141
142
143
144
145
146
def __add__(self, other):
    """Add operator for the time in seconds since 1970-01-01 00:00 UTC"""
    if isinstance(other, Timestamp):
        return self.toTimestamp() + other.toTimestamp()
    elif isinstance(other, (float, int)):
        return self.toTimestamp() + other

    return NotImplemented

__repr__()

Return the time as an ISO 8601 string in UTC

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
133
134
135
136
137
def __repr__(self):
    """Return the time as an ISO 8601 string in UTC"""
    ts = datetime.fromtimestamp(
        self.toTimestamp(), tz=timezone.utc).replace(tzinfo=None)
    return ts.isoformat() + " UTC"

__str__()

See toLocal()

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
129
130
131
def __str__(self):
    """See toLocal()"""
    return self.toLocal()

__sub__(other)

Sub operator for the time in seconds since 1970-01-01 00:00 UTC

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
148
149
150
151
152
153
154
155
def __sub__(self, other):
    """Sub operator for the time in seconds since 1970-01-01 00:00 UTC"""
    if isinstance(other, Timestamp):
        return self.toTimestamp() - other.toTimestamp()
    elif isinstance(other, (float, int)):
        return self.toTimestamp() - other

    return NotImplemented

toLocal(sep='T', timespec='auto')

Return the time as an ISO 8601 string in the local timezone

:param sep: unicode character to separate date and time, default is 'T' :param timespec: specifies the number of additional components of the time to include, defaults to 'auto'. Further options are 'hours', 'minutes', 'seconds', 'milliseconds' and 'microseconds'

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
106
107
108
109
110
111
112
113
114
115
116
117
def toLocal(self, sep="T", timespec="auto"):
    """
    Return the time as an ISO 8601 string in the local timezone

    :param sep: unicode character to separate date and time, default is 'T'
    :param timespec: specifies the number of additional components
                     of the time to include, defaults to 'auto'.
                     Further options are 'hours', 'minutes', 'seconds',
                     'milliseconds' and 'microseconds'
    """
    return datetime.fromtimestamp(self.toTimestamp()).isoformat(
        sep, timespec=timespec)

toTimestamp()

Return the time as seconds since 1970-01-01 00:00 UTC

Source code in src/pythonKarabo/karabo/native/data/timestamp.py
102
103
104
def toTimestamp(self):
    """Return the time as seconds since 1970-01-01 00:00 UTC"""
    return self.time / RESOLUTION