diff --git a/launch/launch/event_handler.py b/launch/launch/event_handler.py index 078da25f5..4c93b6546 100644 --- a/launch/launch/event_handler.py +++ b/launch/launch/event_handler.py @@ -39,7 +39,8 @@ def __init__( self, *, matcher: Callable[[Event], bool], - entities: Optional[SomeActionsType] = None + entities: Optional[SomeActionsType] = None, + handle_once: bool = False ) -> None: """ Constructor. @@ -48,15 +49,33 @@ def __init__( the event should be handled by this event handler, False otherwise. :param: entities is an LaunchDescriptionEntity or list of them, and is returned by handle() unconditionally if matcher returns True. + :param: handle_once is a flag that, if True, unregisters this EventHandler + after being handled once. """ self.__matcher = matcher self.__entities = entities + self.handle_once = handle_once @property def entities(self): """Getter for entities.""" return self.__entities + @property + def handle_once(self): + """Getter for handle_once flag.""" + return self.__handle_once + + @handle_once.setter + def handle_once(self, value): + """Setter for handle_once flag.""" + if isinstance(value, bool): + self.__handle_once = value + else: + raise TypeError( + 'handle_once expects type "bool", but received {} instead.'.format(type(value)) + ) + # TODO(wjwwood): setup standard interface for describing event handlers def matches(self, event: Event) -> bool: @@ -66,4 +85,6 @@ def matches(self, event: Event) -> bool: def handle(self, event: Event, context: 'LaunchContext') -> Optional[SomeActionsType]: """Handle the given event.""" context.extend_locals({'event': event}) + if self.handle_once: + context.unregister_event_handler(self) return self.__entities diff --git a/launch/test/launch/test_event_handler.py b/launch/test/launch/test_event_handler.py index 5a6285a8f..6250a599c 100644 --- a/launch/test/launch/test_event_handler.py +++ b/launch/test/launch/test_event_handler.py @@ -19,11 +19,18 @@ from launch import LaunchDescriptionEntity from launch import SomeActionsType_types_tuple +import pytest + def test_event_handler_constructors(): """Test the constructors for EventHandler class.""" EventHandler(matcher=lambda event: False) EventHandler(matcher=lambda event: False, entities=[LaunchDescriptionEntity]) + EventHandler(matcher=lambda event: True, handle_once=True) + + # Construct with bad type + with pytest.raises(TypeError): + EventHandler(matcher=lambda event: True, handle_once='Bad type') def test_event_handler_matches_and_handle(): @@ -39,3 +46,40 @@ class MockEvent: assert isinstance(entities, SomeActionsType_types_tuple) assert len(entities) == 1 assert context.locals.event == mock_event + + +def test_event_handler_handle_once_property(): + eh = EventHandler(matcher=lambda event: True, handle_once=True) + assert eh.handle_once + eh.handle_once = False + assert not eh.handle_once + # Set bad type + with pytest.raises(TypeError): + eh.handle_once = 'Not a bool' + + +def test_event_handler_handle_once(): + """Test the option for handling events once for the EventHandler class.""" + class MockEvent: + ... + + mock_event = MockEvent() + + # Test handling multiple events with handle_once=False (default) + eh_multiple = EventHandler(matcher=lambda event: True, handle_once=False) + context_multiple = LaunchContext() + context_multiple.register_event_handler(eh_multiple) + eh_multiple.handle(mock_event, context_multiple) + assert context_multiple.locals.event == mock_event + # Attempt to handle a second event + eh_multiple.handle(mock_event, context_multiple) + + # Test handling multiple events with handle_once=True + eh_once = EventHandler(matcher=lambda event: True, handle_once=True) + context_once = LaunchContext() + context_once.register_event_handler(eh_once) + eh_once.handle(mock_event, context_once) + assert context_once.locals.event == mock_event + # Attempt to handle a second event, this time expect ValueError because it is unregistered + with pytest.raises(ValueError): + eh_once.handle(mock_event, context_once)