bt2: add option field class and field support
[babeltrace.git] / src / bindings / python / bt2 / bt2 / field.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2017 Philippe Proulx <pproulx@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 from bt2 import native_bt, object, utils
24 from bt2 import field_class as bt2_field_class
25 import collections.abc
26 import functools
27 import numbers
28 import math
29
30
31 def _create_field_from_ptr(ptr, owner_ptr, owner_get_ref, owner_put_ref):
32 field_class_ptr = native_bt.field_borrow_class_const(ptr)
33 typeid = native_bt.field_class_get_type(field_class_ptr)
34 field = _TYPE_ID_TO_OBJ[typeid]._create_from_ptr_and_get_ref(
35 ptr, owner_ptr, owner_get_ref, owner_put_ref
36 )
37 return field
38
39
40 # Get the "effective" field of `field`. If `field` is a variant, return
41 # the currently selected field. If `field` is an option, return the
42 # content field. If `field` is of any other type, return `field`
43 # directly.
44
45
46 def _get_leaf_field(field):
47 if isinstance(field, _VariantField):
48 return _get_leaf_field(field.selected_option)
49
50 if isinstance(field, _OptionField):
51 return _get_leaf_field(field.field)
52
53 return field
54
55
56 class _Field(object._UniqueObject):
57 def __eq__(self, other):
58 other = _get_leaf_field(other)
59 return self._spec_eq(other)
60
61 @property
62 def field_class(self):
63 field_class_ptr = native_bt.field_borrow_class_const(self._ptr)
64 assert field_class_ptr is not None
65 return bt2_field_class._create_field_class_from_ptr_and_get_ref(field_class_ptr)
66
67 def _repr(self):
68 raise NotImplementedError
69
70 def __repr__(self):
71 return self._repr()
72
73
74 @functools.total_ordering
75 class _NumericField(_Field):
76 @staticmethod
77 def _extract_value(other):
78 if isinstance(other, _BoolField) or isinstance(other, bool):
79 return bool(other)
80
81 if isinstance(other, numbers.Integral):
82 return int(other)
83
84 if isinstance(other, numbers.Real):
85 return float(other)
86
87 if isinstance(other, numbers.Complex):
88 return complex(other)
89
90 raise TypeError(
91 "'{}' object is not a number object".format(other.__class__.__name__)
92 )
93
94 def __int__(self):
95 return int(self._value)
96
97 def __float__(self):
98 return float(self._value)
99
100 def _repr(self):
101 return repr(self._value)
102
103 def __lt__(self, other):
104 if not isinstance(other, numbers.Number):
105 raise TypeError(
106 'unorderable types: {}() < {}()'.format(
107 self.__class__.__name__, other.__class__.__name__
108 )
109 )
110
111 return self._value < self._extract_value(other)
112
113 def _spec_eq(self, other):
114 try:
115 return self._value == self._extract_value(other)
116 except:
117 return False
118
119 def __rmod__(self, other):
120 return self._extract_value(other) % self._value
121
122 def __mod__(self, other):
123 return self._value % self._extract_value(other)
124
125 def __rfloordiv__(self, other):
126 return self._extract_value(other) // self._value
127
128 def __floordiv__(self, other):
129 return self._value // self._extract_value(other)
130
131 def __round__(self, ndigits=None):
132 if ndigits is None:
133 return round(self._value)
134 else:
135 return round(self._value, ndigits)
136
137 def __ceil__(self):
138 return math.ceil(self._value)
139
140 def __floor__(self):
141 return math.floor(self._value)
142
143 def __trunc__(self):
144 return int(self._value)
145
146 def __abs__(self):
147 return abs(self._value)
148
149 def __add__(self, other):
150 return self._value + self._extract_value(other)
151
152 def __radd__(self, other):
153 return self.__add__(other)
154
155 def __neg__(self):
156 return -self._value
157
158 def __pos__(self):
159 return +self._value
160
161 def __mul__(self, other):
162 return self._value * self._extract_value(other)
163
164 def __rmul__(self, other):
165 return self.__mul__(other)
166
167 def __truediv__(self, other):
168 return self._value / self._extract_value(other)
169
170 def __rtruediv__(self, other):
171 return self._extract_value(other) / self._value
172
173 def __pow__(self, exponent):
174 return self._value ** self._extract_value(exponent)
175
176 def __rpow__(self, base):
177 return self._extract_value(base) ** self._value
178
179
180 class _IntegralField(_NumericField, numbers.Integral):
181 def __lshift__(self, other):
182 return self._value << self._extract_value(other)
183
184 def __rlshift__(self, other):
185 return self._extract_value(other) << self._value
186
187 def __rshift__(self, other):
188 return self._value >> self._extract_value(other)
189
190 def __rrshift__(self, other):
191 return self._extract_value(other) >> self._value
192
193 def __and__(self, other):
194 return self._value & self._extract_value(other)
195
196 def __rand__(self, other):
197 return self._extract_value(other) & self._value
198
199 def __xor__(self, other):
200 return self._value ^ self._extract_value(other)
201
202 def __rxor__(self, other):
203 return self._extract_value(other) ^ self._value
204
205 def __or__(self, other):
206 return self._value | self._extract_value(other)
207
208 def __ror__(self, other):
209 return self._extract_value(other) | self._value
210
211 def __invert__(self):
212 return ~self._value
213
214
215 class _BoolField(_IntegralField, _Field):
216 _NAME = 'Boolean'
217
218 def __bool__(self):
219 return self._value
220
221 def _value_to_bool(self, value):
222 if isinstance(value, _BoolField):
223 value = value._value
224
225 if not isinstance(value, bool):
226 raise TypeError(
227 "'{}' object is not a 'bool' or '_BoolField' object".format(
228 value.__class__
229 )
230 )
231
232 return value
233
234 @property
235 def _value(self):
236 return bool(native_bt.field_bool_get_value(self._ptr))
237
238 def _set_value(self, value):
239 value = self._value_to_bool(value)
240 native_bt.field_bool_set_value(self._ptr, value)
241
242 value = property(fset=_set_value)
243
244
245 class _IntegerField(_IntegralField, _Field):
246 pass
247
248
249 class _UnsignedIntegerField(_IntegerField, _Field):
250 _NAME = 'Unsigned integer'
251
252 def _value_to_int(self, value):
253 if not isinstance(value, numbers.Integral):
254 raise TypeError('expecting an integral number object')
255
256 value = int(value)
257 utils._check_uint64(value)
258
259 return value
260
261 @property
262 def _value(self):
263 return native_bt.field_integer_unsigned_get_value(self._ptr)
264
265 def _set_value(self, value):
266 value = self._value_to_int(value)
267 native_bt.field_integer_unsigned_set_value(self._ptr, value)
268
269 value = property(fset=_set_value)
270
271
272 class _SignedIntegerField(_IntegerField, _Field):
273 _NAME = 'Signed integer'
274
275 def _value_to_int(self, value):
276 if not isinstance(value, numbers.Integral):
277 raise TypeError('expecting an integral number object')
278
279 value = int(value)
280 utils._check_int64(value)
281
282 return value
283
284 @property
285 def _value(self):
286 return native_bt.field_integer_signed_get_value(self._ptr)
287
288 def _set_value(self, value):
289 value = self._value_to_int(value)
290 native_bt.field_integer_signed_set_value(self._ptr, value)
291
292 value = property(fset=_set_value)
293
294
295 class _RealField(_NumericField, numbers.Real):
296 _NAME = 'Real'
297
298 def _value_to_float(self, value):
299 if not isinstance(value, numbers.Real):
300 raise TypeError("expecting a real number object")
301
302 return float(value)
303
304 @property
305 def _value(self):
306 return native_bt.field_real_get_value(self._ptr)
307
308 def _set_value(self, value):
309 value = self._value_to_float(value)
310 native_bt.field_real_set_value(self._ptr, value)
311
312 value = property(fset=_set_value)
313
314
315 class _EnumerationField(_IntegerField):
316 def _repr(self):
317 return '{} ({})'.format(self._value, ', '.join(self.labels))
318
319 @property
320 def labels(self):
321 status, labels = self._get_mapping_labels(self._ptr)
322 utils._handle_func_status(status, "cannot get label for enumeration field")
323
324 assert labels is not None
325 return labels
326
327
328 class _UnsignedEnumerationField(_EnumerationField, _UnsignedIntegerField):
329 _NAME = 'Unsigned Enumeration'
330 _get_mapping_labels = staticmethod(
331 native_bt.field_enumeration_unsigned_get_mapping_labels
332 )
333
334
335 class _SignedEnumerationField(_EnumerationField, _SignedIntegerField):
336 _NAME = 'Signed Enumeration'
337 _get_mapping_labels = staticmethod(
338 native_bt.field_enumeration_signed_get_mapping_labels
339 )
340
341
342 @functools.total_ordering
343 class _StringField(_Field):
344 _NAME = 'String'
345
346 def _value_to_str(self, value):
347 if isinstance(value, self.__class__):
348 value = value._value
349
350 if not isinstance(value, str):
351 raise TypeError("expecting a 'str' object")
352
353 return value
354
355 @property
356 def _value(self):
357 return native_bt.field_string_get_value(self._ptr)
358
359 def _set_value(self, value):
360 value = self._value_to_str(value)
361 native_bt.field_string_set_value(self._ptr, value)
362
363 value = property(fset=_set_value)
364
365 def _spec_eq(self, other):
366 try:
367 return self._value == self._value_to_str(other)
368 except:
369 return False
370
371 def __lt__(self, other):
372 return self._value < self._value_to_str(other)
373
374 def __bool__(self):
375 return bool(self._value)
376
377 def _repr(self):
378 return repr(self._value)
379
380 def __str__(self):
381 return str(self._value)
382
383 def __getitem__(self, index):
384 return self._value[index]
385
386 def __len__(self):
387 return native_bt.field_string_get_length(self._ptr)
388
389 def __iadd__(self, value):
390 value = self._value_to_str(value)
391 status = native_bt.field_string_append(self._ptr, value)
392 utils._handle_func_status(
393 status, "cannot append to string field object's value"
394 )
395 return self
396
397
398 class _ContainerField(_Field):
399 def __bool__(self):
400 return len(self) != 0
401
402 def __len__(self):
403 count = self._count()
404 assert count >= 0
405 return count
406
407 def __delitem__(self, index):
408 raise NotImplementedError
409
410
411 class _StructureField(_ContainerField, collections.abc.MutableMapping):
412 _NAME = 'Structure'
413
414 def _count(self):
415 return len(self.field_class)
416
417 def __setitem__(self, key, value):
418 # raises if key is somehow invalid
419 field = self[key]
420
421 # the field's property does the appropriate conversion or raises
422 # the appropriate exception
423 field.value = value
424
425 def __iter__(self):
426 # same name iterator
427 return iter(self.field_class)
428
429 def _spec_eq(self, other):
430 if not isinstance(other, collections.abc.Mapping):
431 return False
432
433 if len(self) != len(other):
434 # early mismatch
435 return False
436
437 for self_key in self:
438 if self_key not in other:
439 return False
440
441 if self[self_key] != other[self_key]:
442 return False
443
444 return True
445
446 def _set_value(self, values):
447 try:
448 for key, value in values.items():
449 self[key].value = value
450 except Exception:
451 raise
452
453 value = property(fset=_set_value)
454
455 def _repr(self):
456 items = ['{}: {}'.format(repr(k), repr(v)) for k, v in self.items()]
457 return '{{{}}}'.format(', '.join(items))
458
459 def __getitem__(self, key):
460 utils._check_str(key)
461 field_ptr = native_bt.field_structure_borrow_member_field_by_name(
462 self._ptr, key
463 )
464
465 if field_ptr is None:
466 raise KeyError(key)
467
468 return _create_field_from_ptr(
469 field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
470 )
471
472 def member_at_index(self, index):
473 utils._check_uint64(index)
474
475 if index >= len(self):
476 raise IndexError
477
478 field_ptr = native_bt.field_structure_borrow_member_field_by_index(
479 self._ptr, index
480 )
481 assert field_ptr is not None
482 return _create_field_from_ptr(
483 field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
484 )
485
486
487 class _OptionField(_Field):
488 _NAME = 'Option'
489
490 @property
491 def field(self):
492 field_ptr = native_bt.field_option_borrow_field_const(self._ptr)
493
494 if field_ptr is None:
495 return
496
497 return _create_field_from_ptr(
498 field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
499 )
500
501 @property
502 def has_field(self):
503 return self.field is not None
504
505 @has_field.setter
506 def has_field(self, value):
507 utils._check_bool(value)
508 native_bt.field_option_set_has_field(self._ptr, value)
509
510 def _spec_eq(self, other):
511 return _get_leaf_field(self) == other
512
513 def __bool__(self):
514 return self.has_field
515
516 def __str__(self):
517 return str(self.field)
518
519 def _repr(self):
520 return repr(self.field)
521
522 def _set_value(self, value):
523 self.has_field = True
524 field = self.field
525 assert field is not None
526 field.value = value
527
528 value = property(fset=_set_value)
529
530
531 class _VariantField(_ContainerField, _Field):
532 _NAME = 'Variant'
533
534 @property
535 def selected_option_index(self):
536 return native_bt.field_variant_get_selected_option_field_index(self._ptr)
537
538 @selected_option_index.setter
539 def selected_option_index(self, index):
540 native_bt.field_variant_select_option_field_by_index(self._ptr, index)
541
542 @property
543 def selected_option(self):
544 # TODO: Is there a way to check if the variant field has a selected_option,
545 # so we can raise an exception instead of hitting a pre-condition check?
546 # If there is something, that check should be added to selected_option_index too.
547 field_ptr = native_bt.field_variant_borrow_selected_option_field(self._ptr)
548
549 return _create_field_from_ptr(
550 field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
551 )
552
553 def _spec_eq(self, other):
554 return _get_leaf_field(self) == other
555
556 def __bool__(self):
557 raise NotImplementedError
558
559 def __str__(self):
560 return str(self.selected_option)
561
562 def _repr(self):
563 return repr(self.selected_option)
564
565 def _set_value(self, value):
566 self.selected_option.value = value
567
568 value = property(fset=_set_value)
569
570
571 class _ArrayField(_ContainerField, _Field, collections.abc.MutableSequence):
572 def _get_length(self):
573 return native_bt.field_array_get_length(self._ptr)
574
575 length = property(fget=_get_length)
576
577 def __getitem__(self, index):
578 if not isinstance(index, numbers.Integral):
579 raise TypeError(
580 "'{}' is not an integral number object: invalid index".format(
581 index.__class__.__name__
582 )
583 )
584
585 index = int(index)
586
587 if index < 0 or index >= len(self):
588 raise IndexError('{} field object index is out of range'.format(self._NAME))
589
590 field_ptr = native_bt.field_array_borrow_element_field_by_index(
591 self._ptr, index
592 )
593 assert field_ptr
594 return _create_field_from_ptr(
595 field_ptr, self._owner_ptr, self._owner_get_ref, self._owner_put_ref
596 )
597
598 def __setitem__(self, index, value):
599 # raises if index is somehow invalid
600 field = self[index]
601
602 if not isinstance(field, (_NumericField, _StringField)):
603 raise TypeError('can only set the value of a number or string field')
604
605 # the field's property does the appropriate conversion or raises
606 # the appropriate exception
607 field.value = value
608
609 def insert(self, index, value):
610 raise NotImplementedError
611
612 def _spec_eq(self, other):
613 if not isinstance(other, collections.abc.Sequence):
614 return False
615
616 if len(self) != len(other):
617 # early mismatch
618 return False
619
620 for self_elem, other_elem in zip(self, other):
621 if self_elem != other_elem:
622 return False
623
624 return True
625
626 def _repr(self):
627 return '[{}]'.format(', '.join([repr(v) for v in self]))
628
629
630 class _StaticArrayField(_ArrayField, _Field):
631 _NAME = 'Static array'
632
633 def _count(self):
634 return native_bt.field_array_get_length(self._ptr)
635
636 def _set_value(self, values):
637 if len(self) != len(values):
638 raise ValueError('expected length of value and array field to match')
639
640 for index, value in enumerate(values):
641 if value is not None:
642 self[index].value = value
643
644 value = property(fset=_set_value)
645
646
647 class _DynamicArrayField(_ArrayField, _Field):
648 _NAME = 'Dynamic array'
649
650 def _count(self):
651 return self.length
652
653 def _set_length(self, length):
654 utils._check_uint64(length)
655 status = native_bt.field_array_dynamic_set_length(self._ptr, length)
656 utils._handle_func_status(status, "cannot set dynamic array length")
657
658 length = property(fget=_ArrayField._get_length, fset=_set_length)
659
660 def _set_value(self, values):
661 if len(values) != self.length:
662 self.length = len(values)
663
664 for index, value in enumerate(values):
665 if value is not None:
666 self[index].value = value
667
668 value = property(fset=_set_value)
669
670
671 _TYPE_ID_TO_OBJ = {
672 native_bt.FIELD_CLASS_TYPE_BOOL: _BoolField,
673 native_bt.FIELD_CLASS_TYPE_UNSIGNED_INTEGER: _UnsignedIntegerField,
674 native_bt.FIELD_CLASS_TYPE_SIGNED_INTEGER: _SignedIntegerField,
675 native_bt.FIELD_CLASS_TYPE_REAL: _RealField,
676 native_bt.FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION: _UnsignedEnumerationField,
677 native_bt.FIELD_CLASS_TYPE_SIGNED_ENUMERATION: _SignedEnumerationField,
678 native_bt.FIELD_CLASS_TYPE_STRING: _StringField,
679 native_bt.FIELD_CLASS_TYPE_STRUCTURE: _StructureField,
680 native_bt.FIELD_CLASS_TYPE_STATIC_ARRAY: _StaticArrayField,
681 native_bt.FIELD_CLASS_TYPE_DYNAMIC_ARRAY: _DynamicArrayField,
682 native_bt.FIELD_CLASS_TYPE_OPTION: _OptionField,
683 native_bt.FIELD_CLASS_TYPE_VARIANT_WITHOUT_SELECTOR: _VariantField,
684 native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_UNSIGNED_SELECTOR: _VariantField,
685 native_bt.FIELD_CLASS_TYPE_VARIANT_WITH_SIGNED_SELECTOR: _VariantField,
686 }
This page took 0.044987 seconds and 5 git commands to generate.