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