From 2e00bc76cf37af167c45253cfc8f8d197222d6b8 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Fri, 31 May 2019 08:59:04 -0400 Subject: [PATCH] bt2: let components attach "user data" to ports The C API allows components to attach "user data" to ports when creating them: extern bt_self_component_status bt_self_component_source_add_output_port( bt_self_component_source *self_component, const char *name, void *user_data, bt_self_component_port_output **self_component_port); This is useful to identify the purpose of a given port. This data can later be fetched when an iterator is created for that port. This patch makes the Python API offer a similar facility. When adding a port, the user can optionally pass an arbitrary Python object as user data: self_port = self._add_output_port('port name', user_data={'foo': 23}) which they can then access using the user_data property of _UserComponentPort: print(self_port.user_data) # {'foo': 23} We confine the user data under the user_data property to avoid clashes with future methods and properties that we might add to _UserComponentPort or its subclasses. A new "in" typemap is created for the "add port" functions to pass the PyObject* representing the user data Python object to the creation function. Without the typemap, SWIG complains that the passed value (a PyObject *) is not of the right type (it expects a void *): TypeError: in method 'self_component_source_add_output_port', argument 3 of type 'void *' If the port is created successfully, it now owns a reference to this Python object. We must reflect that in the Python object's refcount, this is done through an "argout" typemap. In this typemap, we need to check the return value of the function we called. When fetching the user data of a port, we use an "out" typemap to increment the refcount of the returned value. This is because a Python method that returns an object must return a new reference that is transferred to the caller. Change-Id: I0b83454a81e71bd7c2fe9449c7fc65c09f18fcf4 Signed-off-by: Simon Marchi Reviewed-on: https://review.lttng.org/c/babeltrace/+/1359 Tested-by: jenkins Reviewed-by: Philippe Proulx --- bindings/python/bt2/bt2/component.py | 16 ++--- bindings/python/bt2/bt2/native_bt_component.i | 28 ++++++-- bindings/python/bt2/bt2/native_bt_port.i | 19 +++++- bindings/python/bt2/bt2/port.py | 5 ++ .../python/bt2/test_message_iterator.py | 3 +- tests/bindings/python/bt2/test_port.py | 66 +++++++++++++++++++ 6 files changed, 123 insertions(+), 14 deletions(-) diff --git a/bindings/python/bt2/bt2/component.py b/bindings/python/bt2/bt2/component.py index a0855a2e..e26acf61 100644 --- a/bindings/python/bt2/bt2/component.py +++ b/bindings/python/bt2/bt2/component.py @@ -702,10 +702,10 @@ class _UserSourceComponent(_UserComponent, _SourceComponent): get_output_port_count, bt2.port._UserComponentOutputPort) - def _add_output_port(self, name): + def _add_output_port(self, name, user_data=None): utils._check_str(name) fn = native_bt.self_component_source_add_output_port - comp_status, self_port_ptr = fn(self._ptr, name, None) + comp_status, self_port_ptr = fn(self._ptr, name, user_data) _handle_component_status(comp_status, 'cannot add output port to source component object') assert self_port_ptr is not None @@ -740,19 +740,19 @@ class _UserFilterComponent(_UserComponent, _FilterComponent): get_input_port_count, bt2.port._UserComponentInputPort) - def _add_output_port(self, name): + def _add_output_port(self, name, user_data=None): utils._check_str(name) fn = native_bt.self_component_filter_add_output_port - comp_status, self_port_ptr = fn(self._ptr, name, None) + comp_status, self_port_ptr = fn(self._ptr, name, user_data) _handle_component_status(comp_status, 'cannot add output port to filter component object') assert self_port_ptr return bt2.port._UserComponentOutputPort._create_from_ptr(self_port_ptr) - def _add_input_port(self, name): + def _add_input_port(self, name, user_data=None): utils._check_str(name) fn = native_bt.self_component_filter_add_input_port - comp_status, self_port_ptr = fn(self._ptr, name, None) + comp_status, self_port_ptr = fn(self._ptr, name, user_data) _handle_component_status(comp_status, 'cannot add input port to filter component object') assert self_port_ptr @@ -775,10 +775,10 @@ class _UserSinkComponent(_UserComponent, _SinkComponent): get_input_port_count, bt2.port._UserComponentInputPort) - def _add_input_port(self, name): + def _add_input_port(self, name, user_data=None): utils._check_str(name) fn = native_bt.self_component_sink_add_input_port - comp_status, self_port_ptr = fn(self._ptr, name, None) + comp_status, self_port_ptr = fn(self._ptr, name, user_data) _handle_component_status(comp_status, 'cannot add input port to sink component object') assert self_port_ptr diff --git a/bindings/python/bt2/bt2/native_bt_component.i b/bindings/python/bt2/bt2/native_bt_component.i index 6612ad9c..f484263e 100644 --- a/bindings/python/bt2/bt2/native_bt_component.i +++ b/bindings/python/bt2/bt2/native_bt_component.i @@ -62,6 +62,26 @@ } } +/* Typemaps used for user data attached to self component ports. */ + +/* + * The user data Python object is kept as the user data of the port, we pass + * the PyObject pointer directly to the port creation function. + */ +%typemap(in) void *PY_SELF_PORT_USER_DATA { + $1 = $input; +} + +/* + * The port, if created successfully, now owns a reference to the Python object, + * we reflect that here. + */ +%typemap(argout) void *PY_SELF_PORT_USER_DATA { + if (PyLong_AsLong($result) == BT_SELF_COMPONENT_STATUS_OK) { + Py_INCREF($1); + } +} + /* From component-const.h */ extern const char *bt_component_get_name(const bt_component *component); @@ -216,7 +236,7 @@ bt_self_component_source_borrow_output_port_by_index( extern bt_self_component_status bt_self_component_source_add_output_port( bt_self_component_source *self_component, - const char *name, void *user_data, + const char *name, void *PY_SELF_PORT_USER_DATA, bt_self_component_port_output **OUT); /* From self-component-filter.h */ @@ -241,7 +261,7 @@ bt_self_component_filter_borrow_output_port_by_index( extern bt_self_component_status bt_self_component_filter_add_output_port( bt_self_component_filter *self_component, - const char *name, void *data, + const char *name, void *PY_SELF_PORT_USER_DATA, bt_self_component_port_output **OUT); extern bt_self_component_port_input * @@ -257,7 +277,7 @@ bt_self_component_filter_borrow_input_port_by_index( extern bt_self_component_status bt_self_component_filter_add_input_port( bt_self_component_filter *self_component, - const char *name, void *data, + const char *name, void *PY_SELF_PORT_USER_DATA, bt_self_component_port_input **OUT); /* From self-component-sink.h */ @@ -281,5 +301,5 @@ bt_self_component_sink_borrow_input_port_by_index( extern bt_self_component_status bt_self_component_sink_add_input_port( bt_self_component_sink *self_component, - const char *name, void *user_data, + const char *name, void *PY_SELF_PORT_USER_DATA, bt_self_component_port_input **OUT); diff --git a/bindings/python/bt2/bt2/native_bt_port.i b/bindings/python/bt2/bt2/native_bt_port.i index 72854378..873c6136 100644 --- a/bindings/python/bt2/bt2/native_bt_port.i +++ b/bindings/python/bt2/bt2/native_bt_port.i @@ -22,6 +22,23 @@ * THE SOFTWARE. */ +/* + * Typemap for the user data attached to (and owned by) a self component port. + * The pointer saved as the port's user data is directly the PyObject *. + * + * As per the CPython calling convention, we need to return a new reference to + * the returned object, which will be transferred to the caller. The following + * typedef allows us to apply the typemap. + */ +%{ +typedef void *PY_SELF_PORT_USER_DATA; +%} + +%typemap(out) PY_SELF_PORT_USER_DATA { + Py_INCREF($1); + $result = $1; +} + /* From port-const.h */ typedef enum bt_port_type { @@ -77,7 +94,7 @@ const bt_port *bt_self_component_port_as_port( extern bt_self_component *bt_self_component_port_borrow_component( bt_self_component_port *self_port); -extern void *bt_self_component_port_get_data( +extern PY_SELF_PORT_USER_DATA bt_self_component_port_get_data( const bt_self_component_port *self_port); /* From self-component-port-output.h */ diff --git a/bindings/python/bt2/bt2/port.py b/bindings/python/bt2/bt2/port.py index a84edf56..f7c23669 100644 --- a/bindings/python/bt2/bt2/port.py +++ b/bindings/python/bt2/bt2/port.py @@ -103,6 +103,11 @@ class _UserComponentPort(_Port): return bt2.connection._Connection._create_from_ptr_and_get_ref(conn_ptr) + @property + def user_data(self): + ptr = self._as_self_port_ptr(self._ptr) + return native_bt.self_component_port_get_data(ptr) + class _UserComponentInputPort(_UserComponentPort, _InputPort): _as_self_port_ptr = staticmethod(native_bt.self_component_port_input_as_self_component_port) diff --git a/tests/bindings/python/bt2/test_message_iterator.py b/tests/bindings/python/bt2/test_message_iterator.py index 0ad56329..9c8277b7 100644 --- a/tests/bindings/python/bt2/test_message_iterator.py +++ b/tests/bindings/python/bt2/test_message_iterator.py @@ -40,13 +40,14 @@ class UserMessageIteratorTestCase(unittest.TestCase): message_iterator_class=MyIter): def __init__(self, params): nonlocal the_output_port_from_source - the_output_port_from_source = self._add_output_port('out') + the_output_port_from_source = self._add_output_port('out', 'user data') initialized = False graph = self._create_graph(MySource) graph.run() self.assertTrue(initialized) self.assertEqual(the_output_port_from_source.addr, the_output_port_from_iter.addr) + self.assertEqual(the_output_port_from_iter.user_data, 'user data') def test_finalize(self): class MyIter(bt2._UserMessageIterator): diff --git a/tests/bindings/python/bt2/test_port.py b/tests/bindings/python/bt2/test_port.py index 2d203784..4566c3a6 100644 --- a/tests/bindings/python/bt2/test_port.py +++ b/tests/bindings/python/bt2/test_port.py @@ -774,3 +774,69 @@ class PortTestCase(unittest.TestCase): pass self._create_comp(MySink) + + def test_source_self_port_user_data(self): + class MyIter(bt2._UserMessageIterator): + def __next__(self): + raise bt2.Stop + + class MySource(bt2._UserFilterComponent, + message_iterator_class=MyIter): + def __init__(comp_self, params): + nonlocal user_datas + + p = comp_self._add_output_port('port1') + user_datas.append(p.user_data) + p = comp_self._add_output_port('port2', 2) + user_datas.append(p.user_data) + + user_datas = [] + + comp = self._create_comp(MySource) + self.assertEqual(user_datas, [None, 2]) + + def test_filter_self_port_user_data(self): + class MyIter(bt2._UserMessageIterator): + def __next__(self): + raise bt2.Stop + + class MyFilter(bt2._UserFilterComponent, + message_iterator_class=MyIter): + def __init__(comp_self, params): + nonlocal user_datas + + p = comp_self._add_output_port('port1') + user_datas.append(p.user_data) + p = comp_self._add_output_port('port2', 'user data string') + user_datas.append(p.user_data) + + p = comp_self._add_input_port('port3') + user_datas.append(p.user_data) + p = comp_self._add_input_port('port4', user_data={'user data': 'dict'}) + user_datas.append(p.user_data) + + user_datas = [] + + comp = self._create_comp(MyFilter) + self.assertEqual(user_datas, + [None, 'user data string', None, {'user data': 'dict'}]) + + def test_sink_self_port_user_data(self): + class MyIter(bt2._UserMessageIterator): + def __next__(self): + raise bt2.Stop + + class MySink(bt2._UserFilterComponent, + message_iterator_class=MyIter): + def __init__(comp_self, params): + nonlocal user_datas + + p = comp_self._add_input_port('port1') + user_datas.append(p.user_data) + p = comp_self._add_input_port('port2', set()) + user_datas.append(p.user_data) + + user_datas = [] + + comp = self._create_comp(MySink) + self.assertEqual(user_datas, [None, set()]) -- 2.34.1