lib/plugin/plugin.c: do not use G_MODULE_BIND_LOCAL for Python plugin provider
[babeltrace.git] / bindings / python / bt2 / component.py
CommitLineData
81447b5b
PP
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
23from bt2 import native_bt, object, utils
24import bt2.notification_iterator
25import collections.abc
911dec08 26import bt2.values
40910fbb 27import sys
81447b5b
PP
28import bt2
29
30
31# This class wraps a component class pointer. This component class could
32# have been created by Python code, but since we only have the pointer,
33# we can only wrap it in a generic way and lose the original Python
34# class.
35class _GenericComponentClass(object._Object):
36 @property
37 def name(self):
38 return native_bt.component_class_get_name(self._ptr)
39
40 @property
41 def description(self):
42 return native_bt.component_class_get_description(self._ptr)
43
40910fbb
PP
44 @property
45 def help(self):
46 return native_bt.component_class_get_help(self._ptr)
47
a67681c1
PP
48 def query(self, obj, params=None):
49 return _query(self._ptr, obj, params)
911dec08 50
81447b5b
PP
51 def __call__(self, params=None, name=None):
52 params = bt2.create_value(params)
53 comp_ptr = native_bt.component_create_with_init_method_data(self._ptr,
54 name,
55 params._ptr,
56 None)
57
58 if comp_ptr is None:
59 raise bt2.CreationError('cannot create component object')
60
61 return _create_generic_component_from_ptr(comp_ptr)
62
63
64class _GenericSourceComponentClass(_GenericComponentClass):
65 pass
66
67
68class _GenericFilterComponentClass(_GenericComponentClass):
69 pass
70
71
72class _GenericSinkComponentClass(_GenericComponentClass):
73 pass
74
75
76# This class holds the methods which are common to both generic
77# component objects and Python user component objects. They use the
78# internal native _ptr, however it was set, to call native API
79# functions.
80class _CommonComponentMethods:
81 @property
82 def name(self):
83 return native_bt.component_get_name(self._ptr)
84
85 @property
86 def component_class(self):
87 cc_ptr = native_bt.component_get_class(self._ptr)
88 utils._handle_ptr(cc_ptr, "cannot get component object's class object")
89 return _create_generic_component_class_from_ptr(cc_ptr)
90
91 def _handle_status(self, status, gen_error_msg):
92 if status == native_bt.COMPONENT_STATUS_END:
93 raise bt2.Stop
94 elif status == native_bt.COMPONENT_STATUS_AGAIN:
95 raise bt2.TryAgain
96 elif status == native_bt.COMPONENT_STATUS_UNSUPPORTED:
97 raise bt2.UnsupportedFeature
98 elif status < 0:
99 raise bt2.Error(gen_error_msg)
100
101
102class _CommonSourceComponentMethods(_CommonComponentMethods):
103 def create_notification_iterator(self):
104 iter_ptr = native_bt.component_source_create_notification_iterator_with_init_method_data(self._ptr, None)
105
106 if iter_ptr is None:
107 raise bt2.CreationError('cannot create notification iterator object')
108
109 return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(iter_ptr)
110
111
112class _CommonFilterComponentMethods(_CommonComponentMethods):
113 def create_notification_iterator(self):
114 iter_ptr = native_bt.component_filter_create_notification_iterator_with_init_method_data(self._ptr, None)
115
116 if iter_ptr is None:
117 raise bt2.CreationError('cannot create notification iterator object')
118
119 return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(iter_ptr)
120
121 def add_notification_iterator(self, notif_iter):
122 utils._check_type(notif_iter, bt2.notification_iterator._GenericNotificationIteratorMethods)
123 status = native_bt.component_filter_add_iterator(self._ptr, notif_iter._ptr)
124 self._handle_status(status, 'unexpected error: cannot add notification iterator to filter component object')
125
126
127class _CommonSinkComponentMethods(_CommonComponentMethods):
128 def add_notification_iterator(self, notif_iter):
129 utils._check_type(notif_iter, bt2.notification_iterator._GenericNotificationIteratorMethods)
130 status = native_bt.component_sink_add_iterator(self._ptr, notif_iter._ptr)
131 self._handle_status(status, 'unexpected error: cannot add notification iterator to sink component object')
132
133 def consume(self):
134 status = native_bt.component_sink_consume(self._ptr)
135 self._handle_status(status, 'unexpected error: cannot consume sink component object')
136
137
138# This is analogous to _GenericSourceComponentClass, but for source
139# component objects.
140class _GenericSourceComponent(object._Object, _CommonSourceComponentMethods):
141 pass
142
143
144# This is analogous to _GenericFilterComponentClass, but for filter
145# component objects.
146class _GenericFilterComponent(object._Object, _CommonFilterComponentMethods):
147 pass
148
149
150# This is analogous to _GenericSinkComponentClass, but for sink
151# component objects.
152class _GenericSinkComponent(object._Object, _CommonSinkComponentMethods):
153 pass
154
155
156_COMP_CLS_TYPE_TO_GENERIC_COMP_CLS = {
157 native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponent,
158 native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponent,
159 native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponent,
160}
161
162
163_COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_CLS = {
164 native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponentClass,
165 native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponentClass,
166 native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponentClass,
167}
168
169
170def _create_generic_component_from_ptr(ptr):
171 comp_cls_type = native_bt.component_get_class_type(ptr)
172 return _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS[comp_cls_type]._create_from_ptr(ptr)
173
174
175def _create_generic_component_class_from_ptr(ptr):
176 comp_cls_type = native_bt.component_class_get_type(ptr)
177 return _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_CLS[comp_cls_type]._create_from_ptr(ptr)
178
179
40910fbb
PP
180def _trim_docstring(docstring):
181 lines = docstring.expandtabs().splitlines()
182 indent = sys.maxsize
183
184 for line in lines[1:]:
185 stripped = line.lstrip()
186
187 if stripped:
188 indent = min(indent, len(line) - len(stripped))
189
190 trimmed = [lines[0].strip()]
191
192 if indent < sys.maxsize:
193 for line in lines[1:]:
194 trimmed.append(line[indent:].rstrip())
195
196 while trimmed and not trimmed[-1]:
197 trimmed.pop()
198
199 while trimmed and not trimmed[0]:
200 trimmed.pop(0)
201
202 return '\n'.join(trimmed)
203
204
a67681c1
PP
205def _query(comp_cls_ptr, obj, params):
206 utils._check_str(obj)
911dec08
PP
207
208 if params is None:
209 params_ptr = native_bt.value_null
210 else:
211 params = bt2.create_value(params)
212 params_ptr = params._ptr
213
a67681c1 214 results_ptr = native_bt.component_class_query(comp_cls_ptr, obj,
911dec08
PP
215 params_ptr)
216
217 if results_ptr is None:
a67681c1 218 raise bt2.Error('cannot query info with object "{}"'.format(obj))
911dec08
PP
219
220 if results_ptr == native_bt.value_null:
221 return
222
223 return bt2.values._create_from_ptr(results_ptr)
224
225
81447b5b
PP
226# Metaclass for component classes defined by Python code.
227#
228# The Python user can create a standard Python class which inherits one
229# of the three base classes (UserSourceComponent, UserFilterComponent,
230# or UserSinkComponent). Those base classes set this class
231# (_UserComponentType) as their metaclass.
232#
233# Once the body of a user-defined component class is executed, this
234# metaclass is used to create and initialize the class. The metaclass
235# creates a native BT component class of the corresponding type and
236# associates it with this user-defined class. The metaclass also defines
237# class methods like the `name` and `description` properties to match
238# the _GenericComponentClass interface.
239#
240# The component class name which is used is either:
241#
242# * The `name` parameter of the class:
243#
244# class MySink(bt2.SinkComponent, name='my-custom-sink'):
245# ...
246#
247# * If the `name` class parameter is not used: the name of the class
248# itself (`MySink` in the example above).
249#
250# The component class description which is used is the user-defined
251# class's docstring:
252#
253# class MySink(bt2.SinkComponent):
254# 'Description goes here'
255# ...
256#
257# A user-defined Python component class can have an __init__() method
258# which must at least accept the `params` and `name` arguments:
259#
260# def __init__(self, params, name, something_else):
261# ...
262#
263# The user-defined component class can also have a _destroy() method
264# (do NOT use __del__()) to be notified when the component object is
265# (really) destroyed.
266#
267# User-defined source and filter component classes must use the
268# `notification_iterator_class` class parameter to specify the
269# notification iterator class to use for this component class:
270#
271# class MyNotificationIterator(bt2.UserNotificationIterator):
272# ...
273#
274# class MySource(bt2.UserSourceComponent,
275# notification_iterator_class=MyNotificationIterator):
276# ...
277#
278# This notification iterator class must inherit
279# bt2.UserNotificationIterator, and it must define the _get() and
280# _next() methods. The notification iterator class can also define an
281# __init__() method: this method has access to the original Python
282# component object which was used to create it as the `component`
283# property. The notification iterator class can also define a _destroy()
284# method (again, do NOT use __del__()): this is called when the
285# notification iterator is (really) destroyed.
286#
287# When the user-defined class is destroyed, this metaclass's __del__()
288# method is called: the native BT component class pointer is put (not
289# needed anymore, at least not by any Python code since all references
290# are dropped for __del__() to be called).
291class _UserComponentType(type):
292 # __new__() is used to catch custom class parameters
293 def __new__(meta_cls, class_name, bases, attrs, **kwargs):
294 return super().__new__(meta_cls, class_name, bases, attrs)
295
296 def __init__(cls, class_name, bases, namespace, **kwargs):
297 super().__init__(class_name, bases, namespace)
298
299 # skip our own bases; they are never directly instantiated by the user
300 if class_name in ('_UserComponent', 'UserSourceComponent', 'UserFilterComponent', 'UserSinkComponent'):
301 return
302
303 comp_cls_name = kwargs.get('name', class_name)
40910fbb
PP
304 comp_cls_descr = None
305 comp_cls_help = None
306
307 if hasattr(cls, '__doc__') and cls.__doc__ is not None:
308 docstring = _trim_docstring(cls.__doc__)
309 lines = docstring.splitlines()
310
311 if len(lines) >= 1:
312 comp_cls_descr = lines[0]
313
314 if len(lines) >= 3:
315 comp_cls_help = '\n'.join(lines[2:])
316
81447b5b
PP
317 iter_cls = kwargs.get('notification_iterator_class')
318
319 if UserSourceComponent in bases:
320 _UserComponentType._set_iterator_class(cls, iter_cls)
321 has_seek_time = _UserComponentType._has_seek_to_time_method(cls._iter_cls)
322 cc_ptr = native_bt.py3_component_class_source_create(cls,
323 comp_cls_name,
324 comp_cls_descr,
40910fbb 325 comp_cls_help,
81447b5b
PP
326 has_seek_time)
327 elif UserFilterComponent in bases:
328 _UserComponentType._set_iterator_class(cls, iter_cls)
329 has_seek_time = _UserComponentType._has_seek_to_time_method(cls._iter_cls)
330 cc_ptr = native_bt.py3_component_class_filter_create(cls,
331 comp_cls_name,
332 comp_cls_descr,
40910fbb 333 comp_cls_help,
81447b5b
PP
334 has_seek_time)
335 elif UserSinkComponent in bases:
336 if not hasattr(cls, '_consume'):
337 raise bt2.IncompleteUserClassError("cannot create component class '{}': missing a _consume() method".format(class_name))
338
339 cc_ptr = native_bt.py3_component_class_sink_create(cls,
340 comp_cls_name,
40910fbb
PP
341 comp_cls_descr,
342 comp_cls_help)
81447b5b
PP
343 else:
344 raise bt2.IncompleteUserClassError("cannot find a known component class base in the bases of '{}'".format(class_name))
345
346 if cc_ptr is None:
347 raise bt2.CreationError("cannot create component class '{}'".format(class_name))
348
349 cls._cc_ptr = cc_ptr
350
351 def __call__(cls, *args, **kwargs):
352 # create instance
353 self = cls.__new__(cls)
354
355 # assign native component pointer received from caller
356 self._ptr = kwargs.get('__comp_ptr')
357 name = kwargs.get('name')
358
359 if self._ptr is None:
360 # called from Python code
361 self._belongs_to_native_component = False
362
363 # py3_component_create() will call self.__init__() with the
364 # desired arguments and keyword arguments. This is needed
365 # because functions such as
366 # bt_component_sink_set_minimum_input_count() can only be
367 # called _during_ the bt_component_create() call (in
368 # Python words: during self.__init__()).
369 #
370 # The arguments and keyword arguments to use for
371 # self.__init__() are put in the object itself to find them
372 # from the bt_component_create() function.
373 self._init_args = args
374 self._init_kwargs = kwargs
375 native_bt.py3_component_create(cls._cc_ptr, self, name)
376
377 # At this point, self._ptr should be set to non-None. If
378 # it's not, an error occured during the
379 # native_bt.py3_component_create() call. We consider this a
380 # creation error.
381 if self._ptr is None:
382 raise bt2.CreationError("cannot create component object from component class '{}'".format(cls.__name__))
383 else:
384 # Called from non-Python code (within
385 # bt_component_create()): call __init__() here, after
386 # removing the __comp_ptr keyword argument which is just for
387 # this __call__() method.
388 self._belongs_to_native_component = True
389 del kwargs['__comp_ptr']
390
391 # inject `name` into the keyword arguments
392 kwargs['name'] = self.name
393 self.__init__(*args, **kwargs)
394
395 return self
396
397 @staticmethod
398 def _has_seek_to_time_method(iter_cls):
399 return hasattr(iter_cls, '_seek_to_time')
400
401 @staticmethod
402 def _set_iterator_class(cls, iter_cls):
403 if iter_cls is None:
404 raise bt2.IncompleteUserClassError("cannot create component class '{}': missing notification iterator class".format(cls.__name__))
405
406 if not issubclass(iter_cls, bt2.notification_iterator.UserNotificationIterator):
407 raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class does not inherit bt2.UserNotificationIterator".format(cls.__name__))
408
409 if not hasattr(iter_cls, '_get'):
410 raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class is missing a _get() method".format(cls.__name__))
411
412 if not hasattr(iter_cls, '_next'):
413 raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class is missing a _next() method".format(cls.__name__))
414
415 cls._iter_cls = iter_cls
416
417 @property
418 def name(cls):
419 return native_bt.component_class_get_name(cls._cc_ptr)
420
421 @property
422 def description(cls):
423 return native_bt.component_class_get_description(cls._cc_ptr)
424
40910fbb
PP
425 @property
426 def help(cls):
427 return native_bt.component_class_get_help(cls._cc_ptr)
428
81447b5b
PP
429 @property
430 def addr(cls):
431 return int(cls._cc_ptr)
432
a67681c1
PP
433 def query(cls, action, params=None):
434 return _query(cls._cc_ptr, action, params)
911dec08 435
a67681c1 436 def _query_from_bt(cls, action, params):
911dec08 437 # this can raise, in which case the native call to
a67681c1
PP
438 # bt_component_class_query() returns NULL
439 results = cls._query(action, params)
911dec08
PP
440 results = bt2.create_value(results)
441
442 if results is None:
443 results_addr = int(native_bt.value_null)
444 else:
445 # steal the underlying native value object for the caller
446 results_addr = int(results._ptr)
447 results._ptr = None
448
449 return results_addr
450
451 @staticmethod
a67681c1 452 def _query(action, params):
911dec08
PP
453 # BT catches this and returns NULL to the user
454 raise NotImplementedError
455
81447b5b
PP
456 def __del__(cls):
457 if hasattr(cls, '_cc_ptr'):
458 native_bt.put(cls._cc_ptr)
459
460
461class _ComponentInputNotificationIterators(collections.abc.Sequence):
462 def __init__(self, comp, count_func, get_func):
463 self._comp = comp
464 self._count_func = count_func
465 self._get_func = get_func
466
467 def __len__(self):
468 status, count = self._count_func(self._comp._ptr)
469 utils._handle_ret(status, "cannot get component object's input notification iterator count")
470 return count
471
472 def __getitem__(self, index):
473 utils._check_uint64(index)
474
475 if index >= len(self):
476 raise IndexError
477
478 notif_iter_ptr = self._get_func(self._comp._ptr, index)
479 utils._handle_ptr(notif_iter_ptr, "cannot get component object's input notification iterator")
480 return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr)
481
482
483class _UserComponent(metaclass=_UserComponentType):
484 @property
485 def addr(self):
486 return int(self._ptr)
487
488 def __init__(self, *args, **kwargs):
489 pass
490
491 def _destroy(self):
492 pass
493
494 def __del__(self):
495 if not self._belongs_to_native_component:
496 self._belongs_to_native_component = True
497 native_bt.py3_component_on_del(self)
498 native_bt.put(self._ptr)
499
500
501class UserSourceComponent(_UserComponent, _CommonSourceComponentMethods):
502 pass
503
504
505class UserFilterComponent(_UserComponent, _CommonFilterComponentMethods):
506 def _set_minimum_input_notification_iterator_count(self, count):
507 utils._check_uint64(count)
508 status = native_bt.component_filter_set_minimum_input_count(self._ptr, count)
509 self._handle_status(status, "unexpected error: cannot set filter component object's minimum input notification iterator count")
510
511 _minimum_input_notification_iterator_count = property(fset=_set_minimum_input_notification_iterator_count)
512
513 def _set_maximum_input_notification_iterator_count(self, count):
514 utils._check_uint64(count)
515 status = native_bt.component_filter_set_maximum_input_count(self._ptr, count)
516 self._handle_status(status, "unexpected error: cannot set filter component object's maximum input notification iterator count")
517
518 _maximum_input_notification_iterator_count = property(fset=_set_maximum_input_notification_iterator_count)
519
520 @property
521 def _input_notification_iterators(self):
522 return _ComponentInputNotificationIterators(self,
523 native_bt.component_filter_get_input_count,
524 native_bt.component_filter_get_input_iterator_private)
525
526 def _add_iterator_from_bt(self, notif_iter_ptr):
527 notif_iter = bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr)
528 self._add_notification_iterator(notif_iter)
529
530 def _add_notification_iterator(self, notif_iter):
531 pass
532
533
534class UserSinkComponent(_UserComponent, _CommonSinkComponentMethods):
535 def _set_minimum_input_notification_iterator_count(self, count):
536 utils._check_uint64(count)
537 status = native_bt.component_sink_set_minimum_input_count(self._ptr, count)
538 self._handle_status(status, "unexpected error: cannot set sink component object's minimum input notification iterator count")
539
540 _minimum_input_notification_iterator_count = property(fset=_set_minimum_input_notification_iterator_count)
541
542 def _set_maximum_input_notification_iterator_count(self, count):
543 utils._check_uint64(count)
544 status = native_bt.component_sink_set_maximum_input_count(self._ptr, count)
545 self._handle_status(status, "unexpected error: cannot set sink component object's maximum input notification iterator count")
546
547 _maximum_input_notification_iterator_count = property(fset=_set_maximum_input_notification_iterator_count)
548
549 @property
550 def _input_notification_iterators(self):
551 return _ComponentInputNotificationIterators(self,
552 native_bt.component_sink_get_input_count,
553 native_bt.component_sink_get_input_iterator_private)
554
555 def _add_iterator_from_bt(self, notif_iter_ptr):
556 notif_iter = bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr)
557 self._add_notification_iterator(notif_iter)
558
559 def _add_notification_iterator(self, notif_iter):
560 pass
This page took 0.047887 seconds and 4 git commands to generate.