From 694c792bc8f078c02acde68a3390acafbb36b2f4 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Tue, 23 Jul 2019 21:56:58 -0400 Subject: [PATCH] bt2: prepend underscore to exceptions not meant to be raised by user The exceptions Error, MemoryError and LoadingError are only meant to be raised by the Python bindings, in response to corresponding status codes from the Babeltrace API. As per our convention, names of classes not meant to be instantiated directly by the user are prepended with an underscore, so change these accordingly. Note that they are still accessible to the user if they need to catch an exception: try: ... except bt2._Error: ... Change-Id: If094d817dac3c507b6bf3e1e794373f1c7fc33e4 Signed-off-by: Simon Marchi Reviewed-on: https://review.lttng.org/c/babeltrace/+/1752 Reviewed-by: Philippe Proulx Tested-by: jenkins --- src/bindings/python/bt2/bt2/__init__.py.in | 5 +++-- src/bindings/python/bt2/bt2/component.py | 6 +++--- src/bindings/python/bt2/bt2/error.py | 2 +- src/bindings/python/bt2/bt2/field_class.py | 2 +- src/bindings/python/bt2/bt2/graph.py | 8 ++++---- src/bindings/python/bt2/bt2/integer_range_set.py | 2 +- src/bindings/python/bt2/bt2/message_iterator.py | 16 ++++++++-------- .../python/bt2/bt2/native_bt_component_class.i | 16 ++++++++-------- src/bindings/python/bt2/bt2/port.py | 2 +- src/bindings/python/bt2/bt2/query_executor.py | 2 +- src/bindings/python/bt2/bt2/stream.py | 2 +- src/bindings/python/bt2/bt2/trace.py | 2 +- src/bindings/python/bt2/bt2/trace_class.py | 4 ++-- src/bindings/python/bt2/bt2/utils.py | 8 ++++---- src/bindings/python/bt2/bt2/value.py | 2 +- .../bindings/python/bt2/test_component_class.py | 6 +++--- tests/bindings/python/bt2/test_error.py | 14 +++++++------- tests/bindings/python/bt2/test_graph.py | 8 ++++---- .../bindings/python/bt2/test_message_iterator.py | 2 +- tests/bindings/python/bt2/test_query_executor.py | 2 +- 20 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/bindings/python/bt2/bt2/__init__.py.in b/src/bindings/python/bt2/bt2/__init__.py.in index e98e4fb0..8de6ba80 100644 --- a/src/bindings/python/bt2/bt2/__init__.py.in +++ b/src/bindings/python/bt2/bt2/__init__.py.in @@ -38,6 +38,7 @@ from bt2.component import _UserSourceComponent from bt2.connection import * from bt2.connection import _Connection from bt2.error import * +from bt2.error import _Error from bt2.event import _Event from bt2.event_class import * from bt2.field_class import * @@ -68,7 +69,7 @@ from bt2.value import _IntegerValue from bt2.clock_snapshot import _UnknownClockSnapshot -class MemoryError(Error): +class _MemoryError(_Error): '''Raised when an operation fails due to memory issues.''' @@ -108,7 +109,7 @@ class NonexistentClockSnapshot(Exception): pass -class LoadingError(Error): +class _LoadingError(_Error): pass diff --git a/src/bindings/python/bt2/bt2/component.py b/src/bindings/python/bt2/bt2/component.py index cd1cf2dd..6d6afc4b 100644 --- a/src/bindings/python/bt2/bt2/component.py +++ b/src/bindings/python/bt2/bt2/component.py @@ -499,7 +499,7 @@ class _UserComponentType(type): ) if cc_ptr is None: - raise bt2.MemoryError( + raise bt2._MemoryError( "cannot create component class '{}'".format(class_name) ) @@ -677,7 +677,7 @@ class _UserComponent(metaclass=_UserComponentType): tc_ptr = native_bt.trace_class_create(ptr) if tc_ptr is None: - raise bt2.MemoryError('could not create trace class') + raise bt2._MemoryError('could not create trace class') tc = bt2._TraceClass._create_from_ptr(tc_ptr) tc._assigns_automatic_stream_class_id = assigns_automatic_stream_class_id @@ -698,7 +698,7 @@ class _UserComponent(metaclass=_UserComponentType): cc_ptr = native_bt.clock_class_create(ptr) if cc_ptr is None: - raise bt2.MemoryError('could not create clock class') + raise bt2._MemoryError('could not create clock class') cc = bt2.clock_class._ClockClass._create_from_ptr(cc_ptr) diff --git a/src/bindings/python/bt2/bt2/error.py b/src/bindings/python/bt2/bt2/error.py index 7fd46482..f0b9d8e7 100644 --- a/src/bindings/python/bt2/bt2/error.py +++ b/src/bindings/python/bt2/bt2/error.py @@ -154,7 +154,7 @@ _ACTOR_TYPE_TO_CLS = { } -class Error(Exception, abc.Sequence): +class _Error(Exception, abc.Sequence): """ Babeltrace API call error. diff --git a/src/bindings/python/bt2/bt2/field_class.py b/src/bindings/python/bt2/bt2/field_class.py index 7f251368..cfad4c04 100644 --- a/src/bindings/python/bt2/bt2/field_class.py +++ b/src/bindings/python/bt2/bt2/field_class.py @@ -46,7 +46,7 @@ class _FieldClass(object._SharedObject): def _check_create_status(self, ptr): if ptr is None: - raise bt2.MemoryError( + raise bt2._MemoryError( 'cannot create {} field class object'.format(self._NAME.lower()) ) diff --git a/src/bindings/python/bt2/bt2/graph.py b/src/bindings/python/bt2/bt2/graph.py index 23467468..e9659bd2 100644 --- a/src/bindings/python/bt2/bt2/graph.py +++ b/src/bindings/python/bt2/bt2/graph.py @@ -73,7 +73,7 @@ class Graph(object._SharedObject): ptr = native_bt.graph_create() if ptr is None: - raise bt2.MemoryError('cannot create graph object') + raise bt2._MemoryError('cannot create graph object') super().__init__(ptr) @@ -147,7 +147,7 @@ class Graph(object._SharedObject): listener_ids = fn(self._ptr, listener_from_native) if listener_ids is None: - raise bt2.Error('cannot add listener to graph object') + raise bt2._Error('cannot add listener to graph object') return bt2._ListenerHandle(listener_ids, self) @@ -162,7 +162,7 @@ class Graph(object._SharedObject): listener_ids = fn(self._ptr, listener_from_native) if listener_ids is None: - raise bt2.Error('cannot add listener to graph object') + raise bt2._Error('cannot add listener to graph object') return bt2._ListenerHandle(listener_ids, self) @@ -196,6 +196,6 @@ class Graph(object._SharedObject): ) if msg_iter_ptr is None: - raise bt2.MemoryError('cannot create output port message iterator') + raise bt2._MemoryError('cannot create output port message iterator') return bt2.message_iterator._OutputPortMessageIterator(msg_iter_ptr) diff --git a/src/bindings/python/bt2/bt2/integer_range_set.py b/src/bindings/python/bt2/bt2/integer_range_set.py index 913422c1..cee8051b 100644 --- a/src/bindings/python/bt2/bt2/integer_range_set.py +++ b/src/bindings/python/bt2/bt2/integer_range_set.py @@ -72,7 +72,7 @@ class _IntegerRangeSet(object._SharedObject, collections.abc.MutableSet): ptr = self._create_range_set() if ptr is None: - raise bt2.MemoryError('cannot create range set object') + raise bt2._MemoryError('cannot create range set object') super().__init__(ptr) diff --git a/src/bindings/python/bt2/bt2/message_iterator.py b/src/bindings/python/bt2/bt2/message_iterator.py index 4457fc58..93595693 100644 --- a/src/bindings/python/bt2/bt2/message_iterator.py +++ b/src/bindings/python/bt2/bt2/message_iterator.py @@ -220,7 +220,7 @@ class _UserMessageIterator(_MessageIterator): ) if ptr is None: - raise bt2.MemoryError('cannot create event message object') + raise bt2._MemoryError('cannot create event message object') return bt2.message._EventMessage(ptr) @@ -231,7 +231,7 @@ class _UserMessageIterator(_MessageIterator): ) if ptr is None: - raise bt2.MemoryError('cannot create inactivity message object') + raise bt2._MemoryError('cannot create inactivity message object') return bt2.message._MessageIteratorInactivityMessage(ptr) @@ -240,7 +240,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_stream_beginning_create(self._bt_ptr, stream._ptr) if ptr is None: - raise bt2.MemoryError('cannot create stream beginning message object') + raise bt2._MemoryError('cannot create stream beginning message object') msg = bt2.message._StreamBeginningMessage(ptr) @@ -254,7 +254,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_stream_end_create(self._bt_ptr, stream._ptr) if ptr is None: - raise bt2.MemoryError('cannot create stream end message object') + raise bt2._MemoryError('cannot create stream end message object') msg = bt2.message._StreamEndMessage(ptr) @@ -285,7 +285,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_packet_beginning_create(self._bt_ptr, packet._ptr) if ptr is None: - raise bt2.MemoryError('cannot create packet beginning message object') + raise bt2._MemoryError('cannot create packet beginning message object') return bt2.message._PacketBeginningMessage(ptr) @@ -311,7 +311,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_packet_end_create(self._bt_ptr, packet._ptr) if ptr is None: - raise bt2.MemoryError('cannot create packet end message object') + raise bt2._MemoryError('cannot create packet end message object') return bt2.message._PacketEndMessage(ptr) @@ -343,7 +343,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_discarded_events_create(self._bt_ptr, stream._ptr) if ptr is None: - raise bt2.MemoryError('cannot discarded events message object') + raise bt2._MemoryError('cannot discarded events message object') msg = bt2.message._DiscardedEventsMessage(ptr) @@ -380,7 +380,7 @@ class _UserMessageIterator(_MessageIterator): ptr = native_bt.message_discarded_packets_create(self._bt_ptr, stream._ptr) if ptr is None: - raise bt2.MemoryError('cannot discarded packets message object') + raise bt2._MemoryError('cannot discarded packets message object') msg = bt2.message._DiscardedPacketsMessage(ptr) diff --git a/src/bindings/python/bt2/bt2/native_bt_component_class.i b/src/bindings/python/bt2/bt2/native_bt_component_class.i index dac194b3..12a4fa04 100644 --- a/src/bindings/python/bt2/bt2/native_bt_component_class.i +++ b/src/bindings/python/bt2/bt2/native_bt_component_class.i @@ -116,10 +116,10 @@ void bt_bt2_cc_init_from_bt2(void) py_mod_bt2 = PyImport_ImportModule("bt2"); BT_ASSERT(py_mod_bt2); py_mod_bt2_exc_error_type = - PyObject_GetAttrString(py_mod_bt2, "Error"); + PyObject_GetAttrString(py_mod_bt2, "_Error"); BT_ASSERT(py_mod_bt2_exc_error_type); py_mod_bt2_exc_memory_error = - PyObject_GetAttrString(py_mod_bt2, "MemoryError"); + PyObject_GetAttrString(py_mod_bt2, "_MemoryError"); BT_ASSERT(py_mod_bt2_exc_memory_error); py_mod_bt2_exc_try_again_type = PyObject_GetAttrString(py_mod_bt2, "TryAgain"); @@ -198,7 +198,7 @@ void restore_current_thread_error_and_append_exception_chain_recursive( } /* - * If the raised exception is a bt2.Error, restore the wrapped error. + * If the raised exception is a bt2._Error, restore the wrapped error. */ if (PyErr_GivenExceptionMatches(py_exc_value, py_mod_bt2_exc_error_type)) { PyObject *py_error_swig_ptr; @@ -206,7 +206,7 @@ void restore_current_thread_error_and_append_exception_chain_recursive( int ret; /* - * We never raise a bt2.Error with a cause: it should be the + * We never raise a bt2._Error with a cause: it should be the * end of the chain. */ BT_ASSERT(!py_exc_cause_value); @@ -270,21 +270,21 @@ end: * try: * try: * something_that_raises_bt2_error() - * except bt2.Error as e1: + * except bt2._Error as e1: * raise ValueError from e1 * except ValueError as e2: * raise TypeError from e2 * * We will have the following exception chain: * - * TypeError -> ValueError -> bt2.Error + * TypeError -> ValueError -> bt2._Error * * Where the TypeError is the current exception (obtained from PyErr_Fetch). * - * The bt2.Error contains a `struct bt_error *` that used to be the current + * The bt2._Error contains a `struct bt_error *` that used to be the current * thread's error, at the moment the exception was raised. * - * This function gets to the bt2.Error and restores the wrapped + * This function gets to the bt2._Error and restores the wrapped * `struct bt_error *` as the current thread's error. * * Then, for each exception in the chain, starting with the oldest one, it adds diff --git a/src/bindings/python/bt2/bt2/port.py b/src/bindings/python/bt2/bt2/port.py index ecee18ce..d0d65d23 100644 --- a/src/bindings/python/bt2/bt2/port.py +++ b/src/bindings/python/bt2/bt2/port.py @@ -119,7 +119,7 @@ class _UserComponentInputPort(_UserComponentPort, _InputPort): self._ptr ) if msg_iter_ptr is None: - raise bt2.MemoryError('cannot create message iterator object') + raise bt2._MemoryError('cannot create message iterator object') return bt2.message_iterator._UserComponentInputPortMessageIterator(msg_iter_ptr) diff --git a/src/bindings/python/bt2/bt2/query_executor.py b/src/bindings/python/bt2/bt2/query_executor.py index a59bc793..6e2bbc81 100644 --- a/src/bindings/python/bt2/bt2/query_executor.py +++ b/src/bindings/python/bt2/bt2/query_executor.py @@ -34,7 +34,7 @@ class QueryExecutor(object._SharedObject): ptr = native_bt.query_executor_create() if ptr is None: - raise bt2.MemoryError('cannot create query executor object') + raise bt2._MemoryError('cannot create query executor object') super().__init__(ptr) diff --git a/src/bindings/python/bt2/bt2/stream.py b/src/bindings/python/bt2/bt2/stream.py index 2e75990d..2d966509 100644 --- a/src/bindings/python/bt2/bt2/stream.py +++ b/src/bindings/python/bt2/bt2/stream.py @@ -62,6 +62,6 @@ class _Stream(bt2.object._SharedObject): packet_ptr = native_bt.packet_create(self._ptr) if packet_ptr is None: - raise bt2.MemoryError('cannot create packet object') + raise bt2._MemoryError('cannot create packet object') return bt2.packet._Packet._create_from_ptr(packet_ptr) diff --git a/src/bindings/python/bt2/bt2/trace.py b/src/bindings/python/bt2/bt2/trace.py index 5516a986..a90b53ce 100644 --- a/src/bindings/python/bt2/bt2/trace.py +++ b/src/bindings/python/bt2/bt2/trace.py @@ -166,7 +166,7 @@ class _Trace(object._SharedObject, collections.abc.Mapping): ) if stream_ptr is None: - raise bt2.MemoryError('cannot create stream object') + raise bt2._MemoryError('cannot create stream object') stream = bt2.stream._Stream._create_from_ptr(stream_ptr) diff --git a/src/bindings/python/bt2/bt2/trace_class.py b/src/bindings/python/bt2/bt2/trace_class.py index 798dbee0..0acd7694 100644 --- a/src/bindings/python/bt2/bt2/trace_class.py +++ b/src/bindings/python/bt2/bt2/trace_class.py @@ -69,7 +69,7 @@ class _TraceClass(object._SharedObject, collections.abc.Mapping): trace_ptr = native_bt.trace_create(self._ptr) if trace_ptr is None: - raise bt2.MemoryError('cannot create trace class object') + raise bt2._MemoryError('cannot create trace class object') trace = bt2.trace._Trace._create_from_ptr(trace_ptr) @@ -204,7 +204,7 @@ class _TraceClass(object._SharedObject, collections.abc.Mapping): def _check_create_status(self, ptr, type_name): if ptr is None: - raise bt2.MemoryError('cannot create {} field class'.format(type_name)) + raise bt2._MemoryError('cannot create {} field class'.format(type_name)) def _create_integer_field_class( self, create_func, py_cls, type_name, field_value_range, preferred_display_base diff --git a/src/bindings/python/bt2/bt2/utils.py b/src/bindings/python/bt2/bt2/utils.py index 023ff0a8..bb8d1b32 100644 --- a/src/bindings/python/bt2/bt2/utils.py +++ b/src/bindings/python/bt2/bt2/utils.py @@ -137,10 +137,10 @@ def _handle_func_status(status, msg=None): if status == native_bt.__BT_FUNC_STATUS_ERROR: assert msg is not None - raise bt2.Error(msg) + raise bt2._Error(msg) elif status == native_bt.__BT_FUNC_STATUS_MEMORY_ERROR: assert msg is not None - raise bt2.MemoryError(msg) + raise bt2._MemoryError(msg) elif status == native_bt.__BT_FUNC_STATUS_END: if msg is None: raise bt2.Stop @@ -158,9 +158,9 @@ def _handle_func_status(status, msg=None): raise bt2.Canceled(msg) elif status == native_bt.__BT_FUNC_STATUS_LOADING_ERROR: if msg is None: - raise bt2.LoadingError + raise bt2._LoadingError else: - raise bt2.LoadingError(msg) + raise bt2._LoadingError(msg) elif status == native_bt.__BT_FUNC_STATUS_OVERFLOW: if msg is None: raise bt2.OverflowError diff --git a/src/bindings/python/bt2/bt2/value.py b/src/bindings/python/bt2/bt2/value.py index 2c947660..e7b3969b 100644 --- a/src/bindings/python/bt2/bt2/value.py +++ b/src/bindings/python/bt2/bt2/value.py @@ -92,7 +92,7 @@ class _Value(object._SharedObject, metaclass=abc.ABCMeta): def _check_create_status(self, ptr): if ptr is None: - raise bt2.MemoryError( + raise bt2._MemoryError( 'cannot create {} value object'.format(self._NAME.lower()) ) diff --git a/tests/bindings/python/bt2/test_component_class.py b/tests/bindings/python/bt2/test_component_class.py index bbd9774a..7ed554d6 100644 --- a/tests/bindings/python/bt2/test_component_class.py +++ b/tests/bindings/python/bt2/test_component_class.py @@ -214,7 +214,7 @@ class UserComponentClassTestCase(unittest.TestCase): def _graph_is_configured(self): pass - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_raises(self): @@ -229,7 +229,7 @@ class UserComponentClassTestCase(unittest.TestCase): def _query(cls, query_exec, obj, params, log_level): raise ValueError - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_wrong_return_type(self): @@ -244,7 +244,7 @@ class UserComponentClassTestCase(unittest.TestCase): def _query(cls, query_exec, obj, params, log_level): return ... - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_params_none(self): diff --git a/tests/bindings/python/bt2/test_error.py b/tests/bindings/python/bt2/test_error.py index 81b237aa..2f9bebce 100644 --- a/tests/bindings/python/bt2/test_error.py +++ b/tests/bindings/python/bt2/test_error.py @@ -62,7 +62,7 @@ class SinkWithExceptionChaining(bt2._UserSinkComponent): try: print(self._iter.__next__) next(self._iter) - except bt2.Error as e: + except bt2._Error as e: print(hex(id(e))) print(e.__dict__) raise ValueError('oops') from e @@ -82,7 +82,7 @@ class SinkWithFailingQuery(bt2._UserSinkComponent): class ErrorTestCase(unittest.TestCase): def _run_failing_graph(self, source_cc, sink_cc): - with self.assertRaises(bt2.Error) as ctx: + with self.assertRaises(bt2._Error) as ctx: graph = bt2.Graph() src = graph.add_component(source_cc, 'src') snk = graph.add_component(sink_cc, 'snk') @@ -92,7 +92,7 @@ class ErrorTestCase(unittest.TestCase): return ctx.exception def test_current_thread_error_none(self): - # When a bt2.Error is raised, it steals the current thread's error. + # When a bt2._Error is raised, it steals the current thread's error. # Verify that it is now NULL. exc = self._run_failing_graph(SourceWithFailingInit, WorkingSink) self.assertIsNone(native_bt.current_thread_take_error()) @@ -131,10 +131,10 @@ class ErrorTestCase(unittest.TestCase): # # try: # ... - # except bt2.Error as exc: + # except bt2._Error as exc: # raise ValueError('oh noes') from exc # - # We are able to fetch the causes of the original bt2.Error in the + # We are able to fetch the causes of the original bt2._Error in the # exception chain. Also, each exception in the chain should become one # cause once caught. exc = self._run_failing_graph(SourceWithFailingIter, SinkWithExceptionChaining) @@ -150,7 +150,7 @@ class ErrorTestCase(unittest.TestCase): self.assertIsInstance(exc[2], bt2.error._ComponentErrorCause) self.assertEqual(exc[2].component_class_name, 'SinkWithExceptionChaining') self.assertIn( - 'bt2.error.Error: unexpected error: cannot advance the message iterator', + 'bt2.error._Error: unexpected error: cannot advance the message iterator', exc[2].message, ) @@ -186,7 +186,7 @@ class ErrorTestCase(unittest.TestCase): def test_component_class_error_cause(self): q = bt2.QueryExecutor() - with self.assertRaises(bt2.Error) as ctx: + with self.assertRaises(bt2._Error) as ctx: q.query(SinkWithFailingQuery, 'hello') cause = ctx.exception[0] diff --git a/tests/bindings/python/bt2/test_graph.py b/tests/bindings/python/bt2/test_graph.py index 1c183c9b..f6f5dcaa 100644 --- a/tests/bindings/python/bt2/test_graph.py +++ b/tests/bindings/python/bt2/test_graph.py @@ -394,7 +394,7 @@ class GraphTestCase(unittest.TestCase): src.output_ports['out'], sink.input_ports['in'] ) - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): self._graph.run() def test_listeners(self): @@ -510,7 +510,7 @@ class GraphTestCase(unittest.TestCase): graph = bt2.Graph() - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): graph.add_component(MySink, 'comp') def test_raise_in_port_added_listener(self): @@ -530,7 +530,7 @@ class GraphTestCase(unittest.TestCase): graph = bt2.Graph() graph.add_port_added_listener(port_added_listener) - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): graph.add_component(MySink, 'comp') def test_raise_in_ports_connected_listener(self): @@ -562,5 +562,5 @@ class GraphTestCase(unittest.TestCase): up = graph.add_component(MySource, 'down') down = graph.add_component(MySink, 'up') - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): graph.connect_ports(up.output_ports['out'], down.input_ports['in']) diff --git a/tests/bindings/python/bt2/test_message_iterator.py b/tests/bindings/python/bt2/test_message_iterator.py index 72003da7..0f9182f2 100644 --- a/tests/bindings/python/bt2/test_message_iterator.py +++ b/tests/bindings/python/bt2/test_message_iterator.py @@ -297,7 +297,7 @@ class UserMessageIteratorTestCase(unittest.TestCase): MySourceIter._seek_beginning = _seek_beginning_error - with self.assertRaises(bt2.Error): + with self.assertRaises(bt2._Error): it.seek_beginning() # Try consuming many times from an iterator that always returns TryAgain. diff --git a/tests/bindings/python/bt2/test_query_executor.py b/tests/bindings/python/bt2/test_query_executor.py index 9f57f30b..09774309 100644 --- a/tests/bindings/python/bt2/test_query_executor.py +++ b/tests/bindings/python/bt2/test_query_executor.py @@ -97,7 +97,7 @@ class QueryExecutorTestCase(unittest.TestCase): def _query(cls, query_exec, obj, params, log_level): raise ValueError - with self.assertRaises(bt2.Error) as ctx: + with self.assertRaises(bt2._Error) as ctx: res = bt2.QueryExecutor().query(MySink, 'obj', [17, 23]) exc = ctx.exception -- 2.34.1