Build Python bindings with distutils for consistent installs
[babeltrace.git] / bindings / python / bt2 / bt2 / component.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 import bt2.notification_iterator
25 import collections.abc
26 import bt2.values
27 import traceback
28 import bt2.port
29 import sys
30 import bt2
31 import os
32
33
34 _env_var = os.environ.get('BABELTRACE_PYTHON_BT2_NO_TRACEBACK')
35 _NO_PRINT_TRACEBACK = _env_var == '1'
36
37
38 # This class wraps a component class pointer. This component class could
39 # have been created by Python code, but since we only have the pointer,
40 # we can only wrap it in a generic way and lose the original Python
41 # class.
42 class _GenericComponentClass(object._Object):
43 @property
44 def name(self):
45 name = native_bt.component_class_get_name(self._ptr)
46 assert(name is not None)
47 return name
48
49 @property
50 def description(self):
51 return native_bt.component_class_get_description(self._ptr)
52
53 @property
54 def help(self):
55 return native_bt.component_class_get_help(self._ptr)
56
57 def query(self, obj, params=None):
58 return _query(self._ptr, obj, params)
59
60 def __eq__(self, other):
61 if not isinstance(other, _GenericComponentClass):
62 try:
63 if not issubclass(other, _UserComponent):
64 return False
65 except TypeError:
66 return False
67
68 return self.addr == other.addr
69
70
71 class _GenericSourceComponentClass(_GenericComponentClass):
72 pass
73
74
75 class _GenericFilterComponentClass(_GenericComponentClass):
76 pass
77
78
79 class _GenericSinkComponentClass(_GenericComponentClass):
80 pass
81
82
83 def _handle_component_status(status, gen_error_msg):
84 if status == native_bt.COMPONENT_STATUS_END:
85 raise bt2.Stop
86 elif status == native_bt.COMPONENT_STATUS_AGAIN:
87 raise bt2.TryAgain
88 elif status == native_bt.COMPONENT_STATUS_UNSUPPORTED:
89 raise bt2.UnsupportedFeature
90 elif status == native_bt.COMPONENT_STATUS_REFUSE_PORT_CONNECTION:
91 raise bt2.PortConnectionRefused
92 elif status == native_bt.COMPONENT_STATUS_GRAPH_IS_CANCELED:
93 raise bt2.GraphCanceled
94 elif status < 0:
95 raise bt2.Error(gen_error_msg)
96
97
98 class _PortIterator(collections.abc.Iterator):
99 def __init__(self, comp_ports):
100 self._comp_ports = comp_ports
101 self._at = 0
102
103 def __next__(self):
104 if self._at == len(self._comp_ports):
105 raise StopIteration
106
107 comp_ports = self._comp_ports
108 comp_ptr = comp_ports._component._ptr
109 port_ptr = comp_ports._get_port_at_index_fn(comp_ptr, self._at)
110 assert(port_ptr)
111
112 if comp_ports._is_private:
113 port_pub_ptr = native_bt.port_from_private_port(port_ptr)
114 name = native_bt.port_get_name(port_pub_ptr)
115 native_bt.put(port_pub_ptr)
116 else:
117 name = native_bt.port_get_name(port_ptr)
118
119 assert(name is not None)
120 native_bt.put(port_ptr)
121 self._at += 1
122 return name
123
124
125 class _ComponentPorts(collections.abc.Mapping):
126 def __init__(self, is_private, component,
127 get_port_by_name_fn, get_port_at_index_fn,
128 get_port_count_fn):
129 self._is_private = is_private
130 self._component = component
131 self._get_port_by_name_fn = get_port_by_name_fn
132 self._get_port_at_index_fn = get_port_at_index_fn
133 self._get_port_count_fn = get_port_count_fn
134
135 def __getitem__(self, key):
136 utils._check_str(key)
137 port_ptr = self._get_port_by_name_fn(self._component._ptr, key)
138
139 if port_ptr is None:
140 raise KeyError(key)
141
142 if self._is_private:
143 return bt2.port._create_private_from_ptr(port_ptr)
144 else:
145 return bt2.port._create_from_ptr(port_ptr)
146
147 def __len__(self):
148 if self._is_private:
149 pub_ptr = native_bt.component_from_private_component(self._component._ptr)
150 count = self._get_port_count_fn(pub_ptr)
151 native_bt.put(pub_ptr)
152 else:
153 count = self._get_port_count_fn(self._component._ptr)
154
155 assert(count >= 0)
156 return count
157
158 def __iter__(self):
159 return _PortIterator(self)
160
161
162 # This class holds the methods which are common to both generic
163 # component objects and Python user component objects. They use the
164 # internal native _ptr, however it was set, to call native API
165 # functions.
166 class _Component:
167 @property
168 def name(self):
169 name = native_bt.component_get_name(self._ptr)
170 assert(name is not None)
171 return name
172
173 @property
174 def graph(self):
175 ptr = native_bt.component_get_graph(self._ptr)
176 assert(ptr)
177 return bt2.Graph._create_from_ptr(ptr)
178
179 @property
180 def component_class(self):
181 cc_ptr = native_bt.component_get_class(self._ptr)
182 assert(cc_ptr)
183 return _create_generic_component_class_from_ptr(cc_ptr)
184
185 def __eq__(self, other):
186 if not hasattr(other, 'addr'):
187 return False
188
189 return self.addr == other.addr
190
191
192 class _SourceComponent(_Component):
193 pass
194
195
196 class _FilterComponent(_Component):
197 pass
198
199
200 class _SinkComponent(_Component):
201 pass
202
203
204 # This is analogous to _GenericSourceComponentClass, but for source
205 # component objects.
206 class _GenericSourceComponent(object._Object, _SourceComponent):
207 @property
208 def output_ports(self):
209 return _ComponentPorts(False, self,
210 native_bt.component_source_get_output_port_by_name,
211 native_bt.component_source_get_output_port_by_index,
212 native_bt.component_source_get_output_port_count)
213
214
215 # This is analogous to _GenericFilterComponentClass, but for filter
216 # component objects.
217 class _GenericFilterComponent(object._Object, _FilterComponent):
218 @property
219 def output_ports(self):
220 return _ComponentPorts(False, self,
221 native_bt.component_filter_get_output_port_by_name,
222 native_bt.component_filter_get_output_port_by_index,
223 native_bt.component_filter_get_output_port_count)
224
225 @property
226 def input_ports(self):
227 return _ComponentPorts(False, self,
228 native_bt.component_filter_get_input_port_by_name,
229 native_bt.component_filter_get_input_port_by_index,
230 native_bt.component_filter_get_input_port_count)
231
232
233 # This is analogous to _GenericSinkComponentClass, but for sink
234 # component objects.
235 class _GenericSinkComponent(object._Object, _SinkComponent):
236 @property
237 def input_ports(self):
238 return _ComponentPorts(False, self,
239 native_bt.component_sink_get_input_port_by_name,
240 native_bt.component_sink_get_input_port_by_index,
241 native_bt.component_sink_get_input_port_count)
242
243
244 _COMP_CLS_TYPE_TO_GENERIC_COMP_PYCLS = {
245 native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponent,
246 native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponent,
247 native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponent,
248 }
249
250
251 _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_PYCLS = {
252 native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponentClass,
253 native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponentClass,
254 native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponentClass,
255 }
256
257
258 def _create_generic_component_from_ptr(ptr):
259 comp_cls_type = native_bt.component_get_class_type(ptr)
260 return _COMP_CLS_TYPE_TO_GENERIC_COMP_PYCLS[comp_cls_type]._create_from_ptr(ptr)
261
262
263 def _create_generic_component_class_from_ptr(ptr):
264 comp_cls_type = native_bt.component_class_get_type(ptr)
265 return _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_PYCLS[comp_cls_type]._create_from_ptr(ptr)
266
267
268 def _trim_docstring(docstring):
269 lines = docstring.expandtabs().splitlines()
270 indent = sys.maxsize
271
272 for line in lines[1:]:
273 stripped = line.lstrip()
274
275 if stripped:
276 indent = min(indent, len(line) - len(stripped))
277
278 trimmed = [lines[0].strip()]
279
280 if indent < sys.maxsize:
281 for line in lines[1:]:
282 trimmed.append(line[indent:].rstrip())
283
284 while trimmed and not trimmed[-1]:
285 trimmed.pop()
286
287 while trimmed and not trimmed[0]:
288 trimmed.pop(0)
289
290 return '\n'.join(trimmed)
291
292
293 def _query(comp_cls_ptr, obj, params):
294 utils._check_str(obj)
295
296 if params is None:
297 params_ptr = native_bt.value_null
298 else:
299 params = bt2.create_value(params)
300 params_ptr = params._ptr
301
302 results_ptr = native_bt.component_class_query(comp_cls_ptr, obj,
303 params_ptr)
304
305 if results_ptr is None:
306 raise bt2.Error('cannot query info with object "{}"'.format(obj))
307
308 return bt2.values._create_from_ptr(results_ptr)
309
310
311 # Metaclass for component classes defined by Python code.
312 #
313 # The Python user can create a standard Python class which inherits one
314 # of the three base classes (_UserSourceComponent, _UserFilterComponent,
315 # or _UserSinkComponent). Those base classes set this class
316 # (_UserComponentType) as their metaclass.
317 #
318 # Once the body of a user-defined component class is executed, this
319 # metaclass is used to create and initialize the class. The metaclass
320 # creates a native BT component class of the corresponding type and
321 # associates it with this user-defined class. The metaclass also defines
322 # class methods like the `name` and `description` properties to match
323 # the _GenericComponentClass interface.
324 #
325 # The component class name which is used is either:
326 #
327 # * The `name` parameter of the class:
328 #
329 # class MySink(bt2.SinkComponent, name='my-custom-sink'):
330 # ...
331 #
332 # * If the `name` class parameter is not used: the name of the class
333 # itself (`MySink` in the example above).
334 #
335 # The component class description which is used is the user-defined
336 # class's docstring:
337 #
338 # class MySink(bt2.SinkComponent):
339 # 'Description goes here'
340 # ...
341 #
342 # A user-defined Python component class can have an __init__() method
343 # which must at least accept the `params` and `name` arguments:
344 #
345 # def __init__(self, params, name, something_else):
346 # ...
347 #
348 # The user-defined component class can also have a _finalize() method
349 # (do NOT use __del__()) to be notified when the component object is
350 # finalized.
351 #
352 # User-defined source and filter component classes must use the
353 # `notification_iterator_class` class parameter to specify the
354 # notification iterator class to use for this component class:
355 #
356 # class MyNotificationIterator(bt2._UserNotificationIterator):
357 # ...
358 #
359 # class MySource(bt2._UserSourceComponent,
360 # notification_iterator_class=MyNotificationIterator):
361 # ...
362 #
363 # This notification iterator class must inherit
364 # bt2._UserNotificationIterator, and it must define the _get() and
365 # _next() methods. The notification iterator class can also define an
366 # __init__() method: this method has access to the original Python
367 # component object which was used to create it as the `component`
368 # property. The notification iterator class can also define a
369 # _finalize() method (again, do NOT use __del__()): this is called when
370 # the notification iterator is (really) destroyed.
371 #
372 # When the user-defined class is destroyed, this metaclass's __del__()
373 # method is called: the native BT component class pointer is put (not
374 # needed anymore, at least not by any Python code since all references
375 # are dropped for __del__() to be called).
376 class _UserComponentType(type):
377 # __new__() is used to catch custom class parameters
378 def __new__(meta_cls, class_name, bases, attrs, **kwargs):
379 return super().__new__(meta_cls, class_name, bases, attrs)
380
381 def __init__(cls, class_name, bases, namespace, **kwargs):
382 super().__init__(class_name, bases, namespace)
383
384 # skip our own bases; they are never directly instantiated by the user
385 own_bases = (
386 '_UserComponent',
387 '_UserFilterSinkComponent',
388 '_UserSourceComponent',
389 '_UserFilterComponent',
390 '_UserSinkComponent',
391 )
392
393 if class_name in own_bases:
394 return
395
396 comp_cls_name = kwargs.get('name', class_name)
397 utils._check_str(comp_cls_name)
398 comp_cls_descr = None
399 comp_cls_help = None
400
401 if hasattr(cls, '__doc__') and cls.__doc__ is not None:
402 utils._check_str(cls.__doc__)
403 docstring = _trim_docstring(cls.__doc__)
404 lines = docstring.splitlines()
405
406 if len(lines) >= 1:
407 comp_cls_descr = lines[0]
408
409 if len(lines) >= 3:
410 comp_cls_help = '\n'.join(lines[2:])
411
412 iter_cls = kwargs.get('notification_iterator_class')
413
414 if _UserSourceComponent in bases:
415 _UserComponentType._set_iterator_class(cls, iter_cls)
416 cc_ptr = native_bt.py3_component_class_source_create(cls,
417 comp_cls_name,
418 comp_cls_descr,
419 comp_cls_help)
420 elif _UserFilterComponent in bases:
421 _UserComponentType._set_iterator_class(cls, iter_cls)
422 cc_ptr = native_bt.py3_component_class_filter_create(cls,
423 comp_cls_name,
424 comp_cls_descr,
425 comp_cls_help)
426 elif _UserSinkComponent in bases:
427 if not hasattr(cls, '_consume'):
428 raise bt2.IncompleteUserClass("cannot create component class '{}': missing a _consume() method".format(class_name))
429
430 cc_ptr = native_bt.py3_component_class_sink_create(cls,
431 comp_cls_name,
432 comp_cls_descr,
433 comp_cls_help)
434 else:
435 raise bt2.IncompleteUserClass("cannot find a known component class base in the bases of '{}'".format(class_name))
436
437 if cc_ptr is None:
438 raise bt2.CreationError("cannot create component class '{}'".format(class_name))
439
440 cls._cc_ptr = cc_ptr
441
442 def _init_from_native(cls, comp_ptr, params_ptr):
443 # create instance, not user-initialized yet
444 self = cls.__new__(cls)
445
446 # pointer to native private component object (weak/borrowed)
447 self._ptr = comp_ptr
448
449 # call user's __init__() method
450 if params_ptr is not None:
451 native_bt.get(params_ptr)
452 params = bt2.values._create_from_ptr(params_ptr)
453 else:
454 params = None
455
456 self.__init__(params)
457 return self
458
459 def __call__(cls, *args, **kwargs):
460 raise bt2.Error('cannot directly instantiate a user component from a Python module')
461
462 @staticmethod
463 def _set_iterator_class(cls, iter_cls):
464 if iter_cls is None:
465 raise bt2.IncompleteUserClass("cannot create component class '{}': missing notification iterator class".format(cls.__name__))
466
467 if not issubclass(iter_cls, bt2.notification_iterator._UserNotificationIterator):
468 raise bt2.IncompleteUserClass("cannot create component class '{}': notification iterator class does not inherit bt2._UserNotificationIterator".format(cls.__name__))
469
470 if not hasattr(iter_cls, '__next__'):
471 raise bt2.IncompleteUserClass("cannot create component class '{}': notification iterator class is missing a __next__() method".format(cls.__name__))
472
473 cls._iter_cls = iter_cls
474
475 @property
476 def name(cls):
477 return native_bt.component_class_get_name(cls._cc_ptr)
478
479 @property
480 def description(cls):
481 return native_bt.component_class_get_description(cls._cc_ptr)
482
483 @property
484 def help(cls):
485 return native_bt.component_class_get_help(cls._cc_ptr)
486
487 @property
488 def addr(cls):
489 return int(cls._cc_ptr)
490
491 def query(cls, obj, params=None):
492 return _query(cls._cc_ptr, obj, params)
493
494 def _query_from_native(cls, obj, params_ptr):
495 # this can raise, in which case the native call to
496 # bt_component_class_query() returns NULL
497 if params_ptr is not None:
498 native_bt.get(params_ptr)
499 params = bt2.values._create_from_ptr(params_ptr)
500 else:
501 params = None
502
503 try:
504 results = cls._query(obj, params)
505 except:
506 if not _NO_PRINT_TRACEBACK:
507 traceback.print_exc()
508
509 return
510
511 if results is NotImplemented:
512 return results
513
514 try:
515 results = bt2.create_value(results)
516 except:
517 if not _NO_PRINT_TRACEBACK:
518 traceback.print_exc()
519
520 return
521
522 if results is None:
523 results_addr = int(native_bt.value_null)
524 else:
525 # return new reference
526 results._get()
527 results_addr = int(results._ptr)
528
529 return results_addr
530
531 @classmethod
532 def _query(cls, obj, params):
533 # BT catches this and returns NULL to the user
534 return NotImplemented
535
536 def __eq__(self, other):
537 if not hasattr(other, 'addr'):
538 return False
539
540 return self.addr == other.addr
541
542 def __del__(cls):
543 if hasattr(cls, '_cc_ptr'):
544 native_bt.put(cls._cc_ptr)
545
546
547 class _UserComponent(metaclass=_UserComponentType):
548 @property
549 def name(self):
550 pub_ptr = native_bt.component_from_private_component(self._ptr)
551 name = native_bt.component_get_name(pub_ptr)
552 native_bt.put(pub_ptr)
553 assert(name is not None)
554 return name
555
556 @property
557 def graph(self):
558 pub_ptr = native_bt.component_from_private_component(self._ptr)
559 ptr = native_bt.component_get_graph(pub_ptr)
560 native_bt.put(pub_ptr)
561 assert(ptr)
562 return bt2.Graph._create_from_ptr(ptr)
563
564 @property
565 def component_class(self):
566 pub_ptr = native_bt.component_from_private_component(self._ptr)
567 cc_ptr = native_bt.component_get_class(pub_ptr)
568 native_bt.put(pub_ptr)
569 assert(cc_ptr)
570 return _create_generic_component_class_from_ptr(cc_ptr)
571
572 @property
573 def addr(self):
574 return int(self._ptr)
575
576 def __init__(self, params=None):
577 pass
578
579 def _finalize(self):
580 pass
581
582 def _accept_port_connection(self, port, other_port):
583 return True
584
585 def _accept_port_connection_from_native(self, port_ptr, other_port_ptr):
586 native_bt.get(port_ptr)
587 native_bt.get(other_port_ptr)
588 port = bt2.port._create_private_from_ptr(port_ptr)
589 other_port = bt2.port._create_from_ptr(other_port_ptr)
590 res = self._accept_port_connection(port, other_port_ptr)
591
592 if type(res) is not bool:
593 raise TypeError("'{}' is not a 'bool' object")
594
595 return res
596
597 def _port_connected(self, port, other_port):
598 pass
599
600 def _port_connected_from_native(self, port_ptr, other_port_ptr):
601 native_bt.get(port_ptr)
602 native_bt.get(other_port_ptr)
603 port = bt2.port._create_private_from_ptr(port_ptr)
604 other_port = bt2.port._create_from_ptr(other_port_ptr)
605
606 try:
607 self._port_connected(port, other_port_ptr)
608 except:
609 if not _NO_PRINT_TRACEBACK:
610 traceback.print_exc()
611
612 def _port_disconnected(self, port):
613 pass
614
615 def _port_disconnected_from_native(self, port_ptr):
616 native_bt.get(port_ptr)
617 port = bt2.port._create_private_from_ptr(port_ptr)
618
619 try:
620 self._port_disconnected(port)
621 except:
622 if not _NO_PRINT_TRACEBACK:
623 traceback.print_exc()
624
625
626 class _UserSourceComponent(_UserComponent, _SourceComponent):
627 @property
628 def _output_ports(self):
629 return _ComponentPorts(True, self,
630 native_bt.private_component_source_get_output_private_port_by_name,
631 native_bt.private_component_source_get_output_private_port_by_index,
632 native_bt.component_source_get_output_port_count)
633
634 def _add_output_port(self, name):
635 utils._check_str(name)
636 fn = native_bt.private_component_source_add_output_private_port
637 comp_status, priv_port_ptr = fn(self._ptr, name, None)
638 _handle_component_status(comp_status,
639 'cannot add output port to source component object')
640 assert(priv_port_ptr)
641 return bt2.port._create_private_from_ptr(priv_port_ptr)
642
643
644 class _UserFilterComponent(_UserComponent, _FilterComponent):
645 @property
646 def _output_ports(self):
647 return _ComponentPorts(True, self,
648 native_bt.private_component_filter_get_output_private_port_by_name,
649 native_bt.private_component_filter_get_output_private_port_by_index,
650 native_bt.component_filter_get_output_port_count)
651
652 @property
653 def _input_ports(self):
654 return _ComponentPorts(True, self,
655 native_bt.private_component_filter_get_input_private_port_by_name,
656 native_bt.private_component_filter_get_input_private_port_by_index,
657 native_bt.component_filter_get_input_port_count)
658
659 def _add_output_port(self, name):
660 utils._check_str(name)
661 fn = native_bt.private_component_filter_add_output_private_port
662 comp_status, priv_port_ptr = fn(self._ptr, name, None)
663 _handle_component_status(comp_status,
664 'cannot add output port to filter component object')
665 assert(priv_port_ptr)
666 return bt2.port._create_private_from_ptr(priv_port_ptr)
667
668 def _add_input_port(self, name):
669 utils._check_str(name)
670 fn = native_bt.private_component_filter_add_input_private_port
671 comp_status, priv_port_ptr = fn(self._ptr, name, None)
672 _handle_component_status(comp_status,
673 'cannot add input port to filter component object')
674 assert(priv_port_ptr)
675 return bt2.port._create_private_from_ptr(priv_port_ptr)
676
677
678 class _UserSinkComponent(_UserComponent, _SinkComponent):
679 @property
680 def _input_ports(self):
681 return _ComponentPorts(True, self,
682 native_bt.private_component_sink_get_input_private_port_by_name,
683 native_bt.private_component_sink_get_input_private_port_by_index,
684 native_bt.component_sink_get_input_port_count)
685
686 def _add_input_port(self, name):
687 utils._check_str(name)
688 fn = native_bt.private_component_sink_add_input_private_port
689 comp_status, priv_port_ptr = fn(self._ptr, name, None)
690 _handle_component_status(comp_status,
691 'cannot add input port to sink component object')
692 assert(priv_port_ptr)
693 return bt2.port._create_private_from_ptr(priv_port_ptr)
This page took 0.042836 seconds and 4 git commands to generate.