1 # The MIT License (MIT)
3 # Copyright (c) 2017 Philippe Proulx <pproulx@efficios.com>
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:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
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
23 from bt2
import native_bt
, object, utils
24 import bt2
.notification_iterator
25 import collections
.abc
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
35 class _GenericComponentClass(object._Object
):
38 return native_bt
.component_class_get_name(self
._ptr
)
41 def description(self
):
42 return native_bt
.component_class_get_description(self
._ptr
)
46 return native_bt
.component_class_get_help(self
._ptr
)
48 def query(self
, obj
, params
=None):
49 return _query(self
._ptr
, obj
, params
)
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
,
59 raise bt2
.CreationError('cannot create component object')
61 return _create_generic_component_from_ptr(comp_ptr
)
64 class _GenericSourceComponentClass(_GenericComponentClass
):
68 class _GenericFilterComponentClass(_GenericComponentClass
):
72 class _GenericSinkComponentClass(_GenericComponentClass
):
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
80 class _CommonComponentMethods
:
83 return native_bt
.component_get_name(self
._ptr
)
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
)
91 def _handle_status(self
, status
, gen_error_msg
):
92 if status
== native_bt
.COMPONENT_STATUS_END
:
94 elif status
== native_bt
.COMPONENT_STATUS_AGAIN
:
96 elif status
== native_bt
.COMPONENT_STATUS_UNSUPPORTED
:
97 raise bt2
.UnsupportedFeature
99 raise bt2
.Error(gen_error_msg
)
102 class _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)
107 raise bt2
.CreationError('cannot create notification iterator object')
109 return bt2
.notification_iterator
._GenericNotificationIterator
._create
_from
_ptr
(iter_ptr
)
112 class _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)
117 raise bt2
.CreationError('cannot create notification iterator object')
119 return bt2
.notification_iterator
._GenericNotificationIterator
._create
_from
_ptr
(iter_ptr
)
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')
127 class _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')
134 status
= native_bt
.component_sink_consume(self
._ptr
)
135 self
._handle
_status
(status
, 'unexpected error: cannot consume sink component object')
138 # This is analogous to _GenericSourceComponentClass, but for source
140 class _GenericSourceComponent(object._Object
, _CommonSourceComponentMethods
):
144 # This is analogous to _GenericFilterComponentClass, but for filter
146 class _GenericFilterComponent(object._Object
, _CommonFilterComponentMethods
):
150 # This is analogous to _GenericSinkComponentClass, but for sink
152 class _GenericSinkComponent(object._Object
, _CommonSinkComponentMethods
):
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
,
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
,
170 def _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
)
175 def _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
)
180 def _trim_docstring(docstring
):
181 lines
= docstring
.expandtabs().splitlines()
184 for line
in lines
[1:]:
185 stripped
= line
.lstrip()
188 indent
= min(indent
, len(line
) - len(stripped
))
190 trimmed
= [lines
[0].strip()]
192 if indent
< sys
.maxsize
:
193 for line
in lines
[1:]:
194 trimmed
.append(line
[indent
:].rstrip())
196 while trimmed
and not trimmed
[-1]:
199 while trimmed
and not trimmed
[0]:
202 return '\n'.join(trimmed
)
205 def _query(comp_cls_ptr
, obj
, params
):
206 utils
._check
_str
(obj
)
209 params_ptr
= native_bt
.value_null
211 params
= bt2
.create_value(params
)
212 params_ptr
= params
._ptr
214 results_ptr
= native_bt
.component_class_query(comp_cls_ptr
, obj
,
217 if results_ptr
is None:
218 raise bt2
.Error('cannot query info with object "{}"'.format(obj
))
220 if results_ptr
== native_bt
.value_null
:
223 return bt2
.values
._create
_from
_ptr
(results_ptr
)
226 # Metaclass for component classes defined by Python code.
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.
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.
240 # The component class name which is used is either:
242 # * The `name` parameter of the class:
244 # class MySink(bt2.SinkComponent, name='my-custom-sink'):
247 # * If the `name` class parameter is not used: the name of the class
248 # itself (`MySink` in the example above).
250 # The component class description which is used is the user-defined
253 # class MySink(bt2.SinkComponent):
254 # 'Description goes here'
257 # A user-defined Python component class can have an __init__() method
258 # which must at least accept the `params` and `name` arguments:
260 # def __init__(self, params, name, something_else):
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.
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:
271 # class MyNotificationIterator(bt2.UserNotificationIterator):
274 # class MySource(bt2.UserSourceComponent,
275 # notification_iterator_class=MyNotificationIterator):
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.
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).
291 class _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
)
296 def __init__(cls
, class_name
, bases
, namespace
, **kwargs
):
297 super().__init
__(class_name
, bases
, namespace
)
299 # skip our own bases; they are never directly instantiated by the user
300 if class_name
in ('_UserComponent', 'UserSourceComponent', 'UserFilterComponent', 'UserSinkComponent'):
303 comp_cls_name
= kwargs
.get('name', class_name
)
304 comp_cls_descr
= None
307 if hasattr(cls
, '__doc__') and cls
.__doc
__ is not None:
308 docstring
= _trim_docstring(cls
.__doc
__)
309 lines
= docstring
.splitlines()
312 comp_cls_descr
= lines
[0]
315 comp_cls_help
= '\n'.join(lines
[2:])
317 iter_cls
= kwargs
.get('notification_iterator_class')
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
,
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
,
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
))
339 cc_ptr
= native_bt
.py3_component_class_sink_create(cls
,
344 raise bt2
.IncompleteUserClassError("cannot find a known component class base in the bases of '{}'".format(class_name
))
347 raise bt2
.CreationError("cannot create component class '{}'".format(class_name
))
351 def __call__(cls
, *args
, **kwargs
):
353 self
= cls
.__new
__(cls
)
355 # assign native component pointer received from caller
356 self
._ptr
= kwargs
.get('__comp_ptr')
357 name
= kwargs
.get('name')
359 if self
._ptr
is None:
360 # called from Python code
361 self
._belongs
_to
_native
_component
= False
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__()).
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
)
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
381 if self
._ptr
is None:
382 raise bt2
.CreationError("cannot create component object from component class '{}'".format(cls
.__name
__))
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']
391 # inject `name` into the keyword arguments
392 kwargs
['name'] = self
.name
393 self
.__init
__(*args
, **kwargs
)
398 def _has_seek_to_time_method(iter_cls
):
399 return hasattr(iter_cls
, '_seek_to_time')
402 def _set_iterator_class(cls
, iter_cls
):
404 raise bt2
.IncompleteUserClassError("cannot create component class '{}': missing notification iterator class".format(cls
.__name
__))
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
__))
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
__))
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
__))
415 cls
._iter
_cls
= iter_cls
419 return native_bt
.component_class_get_name(cls
._cc
_ptr
)
422 def description(cls
):
423 return native_bt
.component_class_get_description(cls
._cc
_ptr
)
427 return native_bt
.component_class_get_help(cls
._cc
_ptr
)
431 return int(cls
._cc
_ptr
)
433 def query(cls
, action
, params
=None):
434 return _query(cls
._cc
_ptr
, action
, params
)
436 def _query_from_bt(cls
, action
, params
):
437 # this can raise, in which case the native call to
438 # bt_component_class_query() returns NULL
439 results
= cls
._query
(action
, params
)
440 results
= bt2
.create_value(results
)
443 results_addr
= int(native_bt
.value_null
)
445 # steal the underlying native value object for the caller
446 results_addr
= int(results
._ptr
)
452 def _query(action
, params
):
453 # BT catches this and returns NULL to the user
454 raise NotImplementedError
457 if hasattr(cls
, '_cc_ptr'):
458 native_bt
.put(cls
._cc
_ptr
)
461 class _ComponentInputNotificationIterators(collections
.abc
.Sequence
):
462 def __init__(self
, comp
, count_func
, get_func
):
464 self
._count
_func
= count_func
465 self
._get
_func
= get_func
468 status
, count
= self
._count
_func
(self
._comp
._ptr
)
469 utils
._handle
_ret
(status
, "cannot get component object's input notification iterator count")
472 def __getitem__(self
, index
):
473 utils
._check
_uint
64(index
)
475 if index
>= len(self
):
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
)
483 class _UserComponent(metaclass
=_UserComponentType
):
486 return int(self
._ptr
)
488 def __init__(self
, *args
, **kwargs
):
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
)
501 class UserSourceComponent(_UserComponent
, _CommonSourceComponentMethods
):
505 class UserFilterComponent(_UserComponent
, _CommonFilterComponentMethods
):
506 def _set_minimum_input_notification_iterator_count(self
, count
):
507 utils
._check
_uint
64(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")
511 _minimum_input_notification_iterator_count
= property(fset
=_set_minimum_input_notification_iterator_count
)
513 def _set_maximum_input_notification_iterator_count(self
, count
):
514 utils
._check
_uint
64(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")
518 _maximum_input_notification_iterator_count
= property(fset
=_set_maximum_input_notification_iterator_count
)
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
)
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
)
530 def _add_notification_iterator(self
, notif_iter
):
534 class UserSinkComponent(_UserComponent
, _CommonSinkComponentMethods
):
535 def _set_minimum_input_notification_iterator_count(self
, count
):
536 utils
._check
_uint
64(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")
540 _minimum_input_notification_iterator_count
= property(fset
=_set_minimum_input_notification_iterator_count
)
542 def _set_maximum_input_notification_iterator_count(self
, count
):
543 utils
._check
_uint
64(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")
547 _maximum_input_notification_iterator_count
= property(fset
=_set_maximum_input_notification_iterator_count
)
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
)
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
)
559 def _add_notification_iterator(self
, notif_iter
):