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