From b639a2d82cf060b3afe63c8005b337faf5a89b19 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Wed, 18 Mar 2026 01:50:34 +0900 Subject: [PATCH 01/11] Add ament_mypy support and resolve strict type regressions in demo_nodes_py (ros2#767) Resolves #767 by enabling strict type checking. Key Changes: - Added strict annotations and return types to 14+ modules to pass mypy --strict. - Corrected a functional error in matched_event_detect.py where ROS message types were used as type annotations for topic names. - Added test/test_mypy.py and updated package.xml for ament_mypy support. - Ensured node scripts maintain correct #!/usr/bin/env python3 shebangs and LF line endings. Signed-off-by: Jonathan Setiawan --- .../events/matched_event_detect.py | 36 ++++++------ .../logging/use_logger_service.py | 24 +++++--- .../parameters/async_param_client.py | 56 ++++++++++--------- .../parameters/set_parameters_callback.py | 17 ++++-- .../services/add_two_ints_client.py | 6 +- .../services/add_two_ints_client_async.py | 6 +- .../services/add_two_ints_server.py | 16 ++++-- .../demo_nodes_py/services/introspection.py | 49 +++++++++++----- .../demo_nodes_py/topics/listener.py | 10 ++-- .../demo_nodes_py/topics/listener_qos.py | 7 ++- .../topics/listener_serialized.py | 9 +-- demo_nodes_py/demo_nodes_py/topics/talker.py | 12 ++-- .../demo_nodes_py/topics/talker_qos.py | 12 ++-- demo_nodes_py/package.xml | 1 + demo_nodes_py/test/test_copyright.py | 2 +- demo_nodes_py/test/test_flake8.py | 2 +- demo_nodes_py/test/test_mypy.py | 21 +++++++ demo_nodes_py/test/test_pep257.py | 2 +- 18 files changed, 181 insertions(+), 107 deletions(-) create mode 100644 demo_nodes_py/test/test_mypy.py diff --git a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py index 45bbd6db9..3eade3da0 100644 --- a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py +++ b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py @@ -39,7 +39,7 @@ class MatchedEventDetectNode(Node): - def __init__(self, pub_topic_name: String, sub_topic_name: String): + def __init__(self, pub_topic_name: str, sub_topic_name: str): super().__init__('matched_event_detection_node') self.__any_subscription_connected = False # used for publisher event self.__any_publisher_connected = False # used for subscription event @@ -49,10 +49,10 @@ def __init__(self, pub_topic_name: String, sub_topic_name: String): event_callbacks=pub_event_callback) sub_event_callback = SubscriptionEventCallbacks(matched=self.__sub_matched_event_callback) - self.sub = self.create_subscription(String, sub_topic_name, lambda msg: ..., + self.sub = self.create_subscription(String, sub_topic_name, lambda msg: None, 10, event_callbacks=sub_event_callback) - def __pub_matched_event_callback(self, info: QoSPublisherMatchedInfo): + def __pub_matched_event_callback(self, info: QoSPublisherMatchedInfo) -> None: if self.__any_subscription_connected: if info.current_count == 0: self.get_logger().info('Last subscription is disconnected.') @@ -69,7 +69,7 @@ def __pub_matched_event_callback(self, info: QoSPublisherMatchedInfo): self.future.set_result(True) - def __sub_matched_event_callback(self, info: QoSSubscriptionMatchedInfo): + def __sub_matched_event_callback(self, info: QoSSubscriptionMatchedInfo) -> None: if self.__any_publisher_connected: if info.current_count == 0: self.get_logger().info('Last publisher is disconnected.') @@ -86,25 +86,25 @@ def __sub_matched_event_callback(self, info: QoSSubscriptionMatchedInfo): self.future.set_result(True) - def get_future(self): - self.future = Future() + def get_future(self) -> Future: # type: ignore[type-arg] + self.future: Future[bool] = Future() return self.future class MultiSubNode(Node): - def __init__(self, topic_name: String): + def __init__(self, topic_name: str): super().__init__('multi_sub_node') - self.__subs = [] + self.__subs: list[Subscription] = [] # type: ignore[type-arg] self.__topic_name = topic_name - def create_one_sub(self) -> Subscription: + def create_one_sub(self) -> Subscription[String]: self.get_logger().info('Create a new subscription.') - sub = self.create_subscription(String, self.__topic_name, lambda msg: ..., 10) + sub = self.create_subscription(String, self.__topic_name, lambda msg: None, 10) self.__subs.append(sub) return sub - def destroy_one_sub(self, sub: Subscription): + def destroy_one_sub(self, sub: Subscription) -> None: # type: ignore[type-arg] if sub in self.__subs: self.get_logger().info('Destroy a subscription.') @@ -114,18 +114,18 @@ def destroy_one_sub(self, sub: Subscription): class MultiPubNode(Node): - def __init__(self, topic_name: String): + def __init__(self, topic_name: str): super().__init__('multi_pub_node') - self.__pubs = [] + self.__pubs: list[Publisher] = [] # type: ignore[type-arg] self.__topic_name = topic_name - def create_one_pub(self) -> Publisher: + def create_one_pub(self) -> Publisher[String]: self.get_logger().info('Create a new publisher.') pub = self.create_publisher(String, self.__topic_name, 10) self.__pubs.append(pub) return pub - def destroy_one_pub(self, pub: Publisher): + def destroy_one_pub(self, pub: Publisher) -> None: # type: ignore[type-arg] if pub in self.__pubs: self.get_logger().info('Destroy a publisher.') @@ -133,11 +133,11 @@ def destroy_one_pub(self, pub: Publisher): self.destroy_publisher(pub) -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): - topic_name_for_detect_pub_matched_event = 'pub_topic_matched_event_detect' - topic_name_for_detect_sub_matched_event = 'sub_topic_matched_event_detect' + topic_name_for_detect_pub_matched_event: str = 'pub_topic_matched_event_detect' + topic_name_for_detect_sub_matched_event: str = 'sub_topic_matched_event_detect' matched_node = MatchedEventDetectNode( topic_name_for_detect_pub_matched_event, topic_name_for_detect_sub_matched_event) diff --git a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py index ce8aa22be..85ad83e13 100644 --- a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py +++ b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2023 Sony Group Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -66,12 +67,12 @@ class LoggerServiceNode(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('LoggerServiceNode', enable_logger_service=True) self.child_logger = self.get_logger().get_child('child') self.sub = self.create_subscription(String, 'output', self.callback, 10) - def callback(self, msg): + def callback(self, msg: String) -> None: self.get_logger().debug(msg.data + ' with DEBUG logger level.') self.get_logger().info(msg.data + ' with INFO logger level.') self.get_logger().warning(msg.data + ' with WARN logger level.') @@ -84,7 +85,7 @@ def callback(self, msg): class TestNode(Node): - def __init__(self, remote_node_name): + def __init__(self, remote_node_name: str) -> None: super().__init__('TestNode') self.pub = self.create_publisher(String, 'output', 10) self.logger_get_client = self.create_client( @@ -93,7 +94,9 @@ def __init__(self, remote_node_name): SetLoggerLevels, remote_node_name + '/set_logger_levels') self._remote_node_name = remote_node_name - def set_logger_level_on_remote_node(self, logger_level, logger_name='') -> bool: + def set_logger_level_on_remote_node( + self, logger_level: int, logger_name: str = '', + ) -> bool: if not self._logger_set_client.service_is_ready(): return False @@ -101,7 +104,7 @@ def set_logger_level_on_remote_node(self, logger_level, logger_name='') -> bool: set_logger_level = LoggerLevel() set_logger_level.name = logger_name if logger_name else self._remote_node_name set_logger_level.level = logger_level - request.levels.append(set_logger_level) + request.levels.append(set_logger_level) # type: ignore[attr-defined] future = self._logger_set_client.call_async(request) rclpy.spin_until_future_complete(self, future) @@ -117,12 +120,15 @@ def set_logger_level_on_remote_node(self, logger_level, logger_name='') -> bool: return True - def get_logger_level_on_remote_node(self, logger_name=''): + def get_logger_level_on_remote_node( + self, logger_name: str = '', + ) -> list[object]: if not self.logger_get_client.service_is_ready(): return [False, None] request = GetLoggerLevels.Request() - request.names.append(logger_name if logger_name else self._remote_node_name) + name = logger_name if logger_name else self._remote_node_name + request.names.append(name) # type: ignore[attr-defined] future = self.logger_get_client.call_async(request) rclpy.spin_until_future_complete(self, future) @@ -134,7 +140,7 @@ def get_logger_level_on_remote_node(self, logger_name=''): return [True, ret_results.levels[0].level] -def get_logger_level_func(test_node, child_logger_name): +def get_logger_level_func(test_node: TestNode, child_logger_name: str) -> None: ret, level = test_node.get_logger_level_on_remote_node() if ret: test_node.get_logger().info('Current logger level: ' + str(level)) @@ -147,7 +153,7 @@ def get_logger_level_func(test_node, child_logger_name): test_node.get_logger().error('Failed to get child logger level via logger service !') -def main(args=None): +def main(args: list[str] | None = None) -> None: # Check for --service-only flag before ROS 2 consumes the arguments parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--service-only', action='store_true', default=False) diff --git a/demo_nodes_py/demo_nodes_py/parameters/async_param_client.py b/demo_nodes_py/demo_nodes_py/parameters/async_param_client.py index 7b014a027..5a2f4ccbd 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/async_param_client.py +++ b/demo_nodes_py/demo_nodes_py/parameters/async_param_client.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2022 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +24,7 @@ from rclpy.parameter_client import AsyncParameterClient -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(): node = rclpy.create_node('async_param_client') @@ -44,63 +45,64 @@ def main(args=None): rclpy.spin_until_future_complete(node, future) set_parameters_result = future.result() if set_parameters_result is not None: - for i, v in enumerate(set_parameters_result.results): + for i, set_param_result in enumerate(set_parameters_result.results): logger.info(f' {param_list[i]}:') - logger.info(f' successful: {v.successful}') + logger.info(f' successful: {set_param_result.successful}') else: logger.error(f'Error setting parameters: {future.exception()}') logger.info('Listing Parameters:') - future = client.list_parameters(param_list, 10) - rclpy.spin_until_future_complete(node, future) - list_parameters_result = future.result() + list_future = client.list_parameters(param_list, 10) + rclpy.spin_until_future_complete(node, list_future) + list_parameters_result = list_future.result() if list_parameters_result is not None: for param_name in list_parameters_result.result.names: logger.info(f' - {param_name}') else: - logger.error(f'Error listing parameters: {future.exception()}') + logger.error(f'Error listing parameters: {list_future.exception()}') logger.info('Getting parameters:') - future = client.get_parameters(param_list) - rclpy.spin_until_future_complete(node, future) - get_parameters_result = future.result() + get_future = client.get_parameters(param_list) + rclpy.spin_until_future_complete(node, get_future) + get_parameters_result = get_future.result() if get_parameters_result is not None: - for i, v in enumerate(get_parameters_result.values): - logger.info(f' - {param_list[i]}: {parameter_value_to_python(v)}') + for i, get_param_value in enumerate(get_parameters_result.values): + logger.info( + f' - {param_list[i]}: {parameter_value_to_python(get_param_value)}') else: - logger.error(f'Error getting parameters: {future.exception()}') + logger.error(f'Error getting parameters: {get_future.exception()}') logger.info('Loading parameters: ') param_dir = get_package_share_directory('demo_nodes_py') param_file_path = os.path.join(param_dir, 'params.yaml') - future = client.load_parameter_file(param_file_path) - rclpy.spin_until_future_complete(node, future) - load_parameter_results = future.result() + load_future = client.load_parameter_file(param_file_path) + rclpy.spin_until_future_complete(node, load_future) + load_parameter_results = load_future.result() if load_parameter_results is not None: param_file_dict = parameter_dict_from_yaml_file( param_file_path, False, target_nodes=['parameter_blackboard']) - for i, v in enumerate(param_file_dict.keys()): - logger.info(f' {v}:') + for i, k in enumerate(param_file_dict.keys()): + logger.info(f' {k}:') logger.info(f' successful: ' f'{load_parameter_results.results[i].successful}') logger.info(f' value: ' - f'{parameter_value_to_python(param_file_dict[v].value)}') + f'{parameter_value_to_python(param_file_dict[k].value)}') else: - logger.error(f'Error loading parameters: {future.exception()}') + logger.error(f'Error loading parameters: {load_future.exception()}') logger.info('Deleting parameters: ') params_to_delete = ['other_int_parameter', 'other_string_parameter', 'string_parameter'] - future = client.delete_parameters(params_to_delete) - rclpy.spin_until_future_complete(node, future) - delete_parameters_result = future.result() + del_future = client.delete_parameters(params_to_delete) + rclpy.spin_until_future_complete(node, del_future) + delete_parameters_result = del_future.result() if delete_parameters_result is not None: - for i, v in enumerate(delete_parameters_result.results): + for i, del_param_result in enumerate(delete_parameters_result.results): logger.info(f' {params_to_delete[i]}:') - logger.info(f' successful: {v.successful}') - logger.info(f' reason: {v.reason}') + logger.info(f' successful: {del_param_result.successful}') + logger.info(f' reason: {del_param_result.reason}') else: - logger.error(f'Error deleting parameters: {future.exception()}') + logger.error(f'Error deleting parameters: {del_future.exception()}') except (KeyboardInterrupt, ExternalShutdownException): pass diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index 884a74721..934c11b88 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2022 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +29,7 @@ # and post_set parameter callbacks class SetParametersCallback(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('set_parameters_callback') # tracks 'param1' value @@ -38,7 +39,9 @@ def __init__(self): # setting another parameter from the callback is possible # we expect the callback to be called for param2 - def pre_set_parameter_callback(parameter_list): + def pre_set_parameter_callback( + parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> list[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -47,7 +50,9 @@ def pre_set_parameter_callback(parameter_list): return modified_parameters # validation callback - def on_set_parameter_callback(parameter_list): + def on_set_parameter_callback( + parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: if param.name == 'param1': @@ -60,7 +65,9 @@ def on_set_parameter_callback(parameter_list): return result # can change internally tracked class attributes - def post_set_parameter_callback(parameter_list): + def post_set_parameter_callback( + parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> None: for param in parameter_list: if param.name == 'param1': self.internal_tracked_param_1 = param.value @@ -72,7 +79,7 @@ def post_set_parameter_callback(parameter_list): self.add_post_set_parameters_callback(post_set_parameter_callback) -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = SetParametersCallback() diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py index 07ca330dc..e047f445b 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +19,7 @@ from rclpy.executors import ExternalShutdownException -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') @@ -32,7 +33,8 @@ def main(args=None): future = cli.call_async(req) rclpy.spin_until_future_complete(node, future) if future.result() is not None: - node.get_logger().info('Result of add_two_ints: %d' % future.result().sum) + res_sum = future.result().sum # type: ignore[union-attr] + node.get_logger().info('Result of add_two_ints: %d' % res_sum) else: node.get_logger().error('Exception while calling service: %r' % future.exception()) except (KeyboardInterrupt, ExternalShutdownException): diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py index 8412df405..7804286e1 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +19,7 @@ from rclpy.executors import ExternalShutdownException -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') @@ -38,7 +39,8 @@ def main(args=None): rclpy.spin_once(node) if future.done(): if future.result() is not None: - logger.info('Result of add_two_ints: %d' % future.result().sum) + res_sum = future.result().sum # type: ignore[union-attr] + logger.info('Result of add_two_ints: %d' % res_sum) else: logger.error('Exception while calling service: %r' % future.exception()) break diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_server.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_server.py index 92799b666..478a71473 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_server.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_server.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +22,23 @@ class AddTwoIntsServer(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('add_two_ints_server') - self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback) - - def add_two_ints_callback(self, request, response): + self.srv = self.create_service( + AddTwoInts, 'add_two_ints', self.add_two_ints_callback) + + def add_two_ints_callback( + self, + request: AddTwoInts.Request, + response: AddTwoInts.Response, + ) -> AddTwoInts.Response: response.sum = request.a + request.b self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b)) return response -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = AddTwoIntsServer() diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 4712902ff..59985fab2 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -1,4 +1,5 @@ -# Copyright 2023 Open Source Robotics Foundation, Inc. +#!/usr/bin/env python3 +# Copyright 2022 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +23,8 @@ from rclpy.parameter import Parameter from rclpy.qos import qos_profile_system_default from rclpy.service_introspection import ServiceIntrospectionState +from rclpy.task import Future +from rclpy.timer import Timer # This demo program shows how to configure client and service introspection @@ -69,7 +72,9 @@ # In either case, service introspection data can be seen by running: # ros2 topic echo /add_two_ints/_service_event -def check_parameter(parameter_list, parameter_name): +def check_parameter( + parameter_list: list[Parameter], parameter_name: str # type: ignore[type-arg] +) -> SetParametersResult: result = SetParametersResult() result.successful = True for param in parameter_list: @@ -91,10 +96,14 @@ def check_parameter(parameter_list, parameter_name): class IntrospectionClientNode(Node): - def on_set_parameters_callback(self, parameter_list): + def on_set_parameters_callback( + self, parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') - def on_post_set_parameters_callback(self, parameter_list): + def on_post_set_parameters_callback( + self, parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': continue @@ -111,7 +120,7 @@ def on_post_set_parameters_callback(self, parameter_list): introspection_state) break - def __init__(self): + def __init__(self) -> None: super().__init__('introspection_client') self.cli = self.create_client(AddTwoInts, 'add_two_ints') @@ -120,10 +129,10 @@ def __init__(self): self.add_post_set_parameters_callback(self.on_post_set_parameters_callback) self.declare_parameter('client_configure_introspection', 'disabled') - self.timer = self.create_timer(0.5, self.timer_callback) - self.future = None + self.timer: Timer = self.create_timer(0.5, self.timer_callback) + self.future: Future[AddTwoInts.Response] | None = None - def timer_callback(self): + def timer_callback(self) -> None: if not self.cli.service_is_ready(): return @@ -140,7 +149,8 @@ def timer_callback(self): return if self.future.result() is not None: - self.get_logger().info('Result of add_two_ints: %d' % self.future.result().sum) + res_sum = self.future.result().sum # type: ignore[union-attr] + self.get_logger().info('Result of add_two_ints: %d' % res_sum) else: self.get_logger().error('Exception calling service: %r' % self.future.exception()) @@ -149,10 +159,14 @@ def timer_callback(self): class IntrospectionServiceNode(Node): - def on_set_parameters_callback(self, parameter_list): + def on_set_parameters_callback( + self, parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') - def on_post_set_parameters_callback(self, parameter_list): + def on_post_set_parameters_callback( + self, parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': continue @@ -169,23 +183,28 @@ def on_post_set_parameters_callback(self, parameter_list): introspection_state) break - def __init__(self): + def __init__(self) -> None: super().__init__('introspection_service') - self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback) + self.srv = self.create_service( + AddTwoInts, 'add_two_ints', self.add_two_ints_callback) self.add_on_set_parameters_callback(self.on_set_parameters_callback) self.add_post_set_parameters_callback(self.on_post_set_parameters_callback) self.declare_parameter('service_configure_introspection', 'disabled') - def add_two_ints_callback(self, request, response): + def add_two_ints_callback( + self, + request: AddTwoInts.Request, + response: AddTwoInts.Response, + ) -> AddTwoInts.Response: response.sum = request.a + request.b self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b)) return response -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): service_node = IntrospectionServiceNode() diff --git a/demo_nodes_py/demo_nodes_py/topics/listener.py b/demo_nodes_py/demo_nodes_py/topics/listener.py index 9f4ea6171..be46db9f9 100644 --- a/demo_nodes_py/demo_nodes_py/topics/listener.py +++ b/demo_nodes_py/demo_nodes_py/topics/listener.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,15 +22,16 @@ class Listener(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('listener') - self.sub = self.create_subscription(String, 'chatter', self.chatter_callback, 10) + self.sub = self.create_subscription( + String, 'chatter', self.chatter_callback, 10) - def chatter_callback(self, msg): + def chatter_callback(self, msg: String) -> None: self.get_logger().info('I heard: [%s]' % msg.data) -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = Listener() diff --git a/demo_nodes_py/demo_nodes_py/topics/listener_qos.py b/demo_nodes_py/demo_nodes_py/topics/listener_qos.py index 95e134591..40e7f3802 100644 --- a/demo_nodes_py/demo_nodes_py/topics/listener_qos.py +++ b/demo_nodes_py/demo_nodes_py/topics/listener_qos.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +29,7 @@ class ListenerQos(Node): - def __init__(self, qos_profile): + def __init__(self, qos_profile: QoSProfile) -> None: super().__init__('listener_qos') if qos_profile.reliability is QoSReliabilityPolicy.RELIABLE: self.get_logger().info('Reliable listener') @@ -37,11 +38,11 @@ def __init__(self, qos_profile): self.sub = self.create_subscription( String, 'chatter', self.chatter_callback, qos_profile) - def chatter_callback(self, msg): + def chatter_callback(self, msg: String) -> None: self.get_logger().info('I heard: [%s]' % msg.data) -def main(argv=sys.argv[1:]): +def main(argv: list[str] = sys.argv[1:]) -> None: parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '--reliable', dest='reliable', action='store_true', diff --git a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py index 25d49fc99..cdf45f3ff 100644 --- a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py +++ b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2018 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +22,7 @@ class SerializedSubscriber(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('serialized_subscriber') self.subscription = self.create_subscription( String, @@ -32,11 +33,11 @@ def __init__(self): # We're subscribing to the serialized bytes, # not example_interfaces.msg.String - def listener_callback(self, msg): - self.get_logger().info('I heard: "%s"' % msg) + def listener_callback(self, msg: bytes) -> None: + self.get_logger().info('I heard: "%r"' % msg) -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): serialized_subscriber = SerializedSubscriber() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker.py b/demo_nodes_py/demo_nodes_py/topics/talker.py index 026b22d10..a596ab8f8 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,18 +18,19 @@ import rclpy from rclpy.executors import ExternalShutdownException from rclpy.node import Node +from rclpy.timer import Timer class Talker(Node): - def __init__(self): + def __init__(self) -> None: super().__init__('talker') - self.i = 0 + self.i: int = 0 self.pub = self.create_publisher(String, 'chatter', 10) timer_period = 1.0 - self.tmr = self.create_timer(timer_period, self.timer_callback) + self.tmr: Timer = self.create_timer(timer_period, self.timer_callback) - def timer_callback(self): + def timer_callback(self) -> None: msg = String() msg.data = 'Hello World: {0}'.format(self.i) self.i += 1 @@ -36,7 +38,7 @@ def timer_callback(self): self.pub.publish(msg) -def main(args=None): +def main(args: list[str] | None = None) -> None: try: with rclpy.init(args=args): node = Talker() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py index 99c695e00..255d813a4 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,14 +24,15 @@ from rclpy.qos import qos_profile_sensor_data from rclpy.qos import QoSProfile from rclpy.qos import QoSReliabilityPolicy +from rclpy.timer import Timer from rclpy.utilities import remove_ros_args class TalkerQos(Node): - def __init__(self, qos_profile): + def __init__(self, qos_profile: QoSProfile) -> None: super().__init__('talker_qos') - self.i = 0 + self.i: int = 0 if qos_profile.reliability is QoSReliabilityPolicy.RELIABLE: self.get_logger().info('Reliable talker') else: @@ -38,9 +40,9 @@ def __init__(self, qos_profile): self.pub = self.create_publisher(String, 'chatter', qos_profile) timer_period = 1.0 - self.tmr = self.create_timer(timer_period, self.timer_callback) + self.tmr: Timer = self.create_timer(timer_period, self.timer_callback) - def timer_callback(self): + def timer_callback(self) -> None: msg = String() msg.data = 'Hello World: {0}'.format(self.i) self.i += 1 @@ -48,7 +50,7 @@ def timer_callback(self): self.pub.publish(msg) -def main(argv=sys.argv[1:]): +def main(argv: list[str] = sys.argv[1:]) -> None: parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '--reliable', dest='reliable', action='store_true', diff --git a/demo_nodes_py/package.xml b/demo_nodes_py/package.xml index aa7e3ebde..bc9b4a113 100644 --- a/demo_nodes_py/package.xml +++ b/demo_nodes_py/package.xml @@ -26,6 +26,7 @@ ament_copyright ament_flake8 ament_pep257 + ament_mypy ament_xmllint python3-pytest diff --git a/demo_nodes_py/test/test_copyright.py b/demo_nodes_py/test/test_copyright.py index cc8ff03f7..1f8698345 100644 --- a/demo_nodes_py/test/test_copyright.py +++ b/demo_nodes_py/test/test_copyright.py @@ -18,6 +18,6 @@ @pytest.mark.copyright @pytest.mark.linter -def test_copyright(): +def test_copyright() -> None: rc = main(argv=['.', 'test']) assert rc == 0, 'Found errors' diff --git a/demo_nodes_py/test/test_flake8.py b/demo_nodes_py/test/test_flake8.py index 27ee1078f..eac16eef9 100644 --- a/demo_nodes_py/test/test_flake8.py +++ b/demo_nodes_py/test/test_flake8.py @@ -18,7 +18,7 @@ @pytest.mark.flake8 @pytest.mark.linter -def test_flake8(): +def test_flake8() -> None: rc, errors = main_with_errors(argv=[]) assert rc == 0, \ 'Found %d code style errors / warnings:\n' % len(errors) + \ diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py new file mode 100644 index 000000000..9e7660fd2 --- /dev/null +++ b/demo_nodes_py/test/test_mypy.py @@ -0,0 +1,21 @@ +# Copyright 2026 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from ament_mypy.main import main + + +def test_mypy() -> None: + rc = main(argv=[]) + assert rc == 0, 'Found code style errors / warnings' diff --git a/demo_nodes_py/test/test_pep257.py b/demo_nodes_py/test/test_pep257.py index b234a3840..b6808e1d8 100644 --- a/demo_nodes_py/test/test_pep257.py +++ b/demo_nodes_py/test/test_pep257.py @@ -18,6 +18,6 @@ @pytest.mark.linter @pytest.mark.pep257 -def test_pep257(): +def test_pep257() -> None: rc = main(argv=['.', 'test']) assert rc == 0, 'Found code style errors / warnings' From 08df12c287f84fdda53be20796c3bc37fad7f488 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Wed, 18 Mar 2026 09:19:24 +0900 Subject: [PATCH 02/11] fix types for python 3.9, add safety checks, and use --ament-strict Signed-off-by: Jonathan Setiawan --- .../events/matched_event_detect.py | 19 ++++++++------ .../logging/use_logger_service.py | 25 +++++++++++-------- .../parameters/set_parameters_callback.py | 13 ++++++---- .../services/add_two_ints_client.py | 10 +++++--- .../services/add_two_ints_client_async.py | 10 +++++--- .../demo_nodes_py/services/introspection.py | 17 +++++++------ demo_nodes_py/demo_nodes_py/topics/talker.py | 8 +++--- .../demo_nodes_py/topics/talker_qos.py | 6 ++--- demo_nodes_py/test/test_mypy.py | 2 +- 9 files changed, 67 insertions(+), 43 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py index 3eade3da0..69cb81dae 100644 --- a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py +++ b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from example_interfaces.msg import String import rclpy from rclpy.event_handler import PublisherEventCallbacks @@ -86,7 +89,7 @@ def __sub_matched_event_callback(self, info: QoSSubscriptionMatchedInfo) -> None self.future.set_result(True) - def get_future(self) -> Future: # type: ignore[type-arg] + def get_future(self) -> Future[bool]: self.future: Future[bool] = Future() return self.future @@ -95,7 +98,7 @@ class MultiSubNode(Node): def __init__(self, topic_name: str): super().__init__('multi_sub_node') - self.__subs: list[Subscription] = [] # type: ignore[type-arg] + self.__subs: List[Subscription[String]] = [] self.__topic_name = topic_name def create_one_sub(self) -> Subscription[String]: @@ -104,7 +107,7 @@ def create_one_sub(self) -> Subscription[String]: self.__subs.append(sub) return sub - def destroy_one_sub(self, sub: Subscription) -> None: # type: ignore[type-arg] + def destroy_one_sub(self, sub: Subscription[String]) -> None: if sub in self.__subs: self.get_logger().info('Destroy a subscription.') @@ -116,7 +119,7 @@ class MultiPubNode(Node): def __init__(self, topic_name: str): super().__init__('multi_pub_node') - self.__pubs: list[Publisher] = [] # type: ignore[type-arg] + self.__pubs: List[Publisher[String]] = [] self.__topic_name = topic_name def create_one_pub(self) -> Publisher[String]: @@ -125,7 +128,7 @@ def create_one_pub(self) -> Publisher[String]: self.__pubs.append(pub) return pub - def destroy_one_pub(self, pub: Publisher) -> None: # type: ignore[type-arg] + def destroy_one_pub(self, pub: Publisher[String]) -> None: if pub in self.__pubs: self.get_logger().info('Destroy a publisher.') @@ -133,11 +136,11 @@ def destroy_one_pub(self, pub: Publisher) -> None: # type: ignore[type-arg] self.destroy_publisher(pub) -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): - topic_name_for_detect_pub_matched_event: str = 'pub_topic_matched_event_detect' - topic_name_for_detect_sub_matched_event: str = 'sub_topic_matched_event_detect' + topic_name_for_detect_pub_matched_event = 'pub_topic_matched_event_detect' + topic_name_for_detect_sub_matched_event = 'sub_topic_matched_event_detect' matched_node = MatchedEventDetectNode( topic_name_for_detect_pub_matched_event, topic_name_for_detect_sub_matched_event) diff --git a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py index 85ad83e13..7ec1c05eb 100644 --- a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py +++ b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py @@ -12,10 +12,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - import argparse import threading import time +from typing import List +from typing import Literal +from typing import Tuple +from typing import Union from example_interfaces.msg import String from rcl_interfaces.msg import LoggerLevel @@ -104,7 +107,7 @@ def set_logger_level_on_remote_node( set_logger_level = LoggerLevel() set_logger_level.name = logger_name if logger_name else self._remote_node_name set_logger_level.level = logger_level - request.levels.append(set_logger_level) # type: ignore[attr-defined] + request.levels = [set_logger_level] future = self._logger_set_client.call_async(request) rclpy.spin_until_future_complete(self, future) @@ -122,38 +125,40 @@ def set_logger_level_on_remote_node( def get_logger_level_on_remote_node( self, logger_name: str = '', - ) -> list[object]: + ) -> List[Union[Tuple[Literal[False], None], Tuple[Literal[True], int]]]: if not self.logger_get_client.service_is_ready(): - return [False, None] + return [(False, None)] request = GetLoggerLevels.Request() name = logger_name if logger_name else self._remote_node_name - request.names.append(name) # type: ignore[attr-defined] + request.names = [name] future = self.logger_get_client.call_async(request) rclpy.spin_until_future_complete(self, future) ret_results = future.result() if not ret_results: - return [False, None] + return [(False, None)] - return [True, ret_results.levels[0].level] + return [(True, ret_results.levels[0].level)] def get_logger_level_func(test_node: TestNode, child_logger_name: str) -> None: - ret, level = test_node.get_logger_level_on_remote_node() + results = test_node.get_logger_level_on_remote_node() + ret, level = results[0] if ret: test_node.get_logger().info('Current logger level: ' + str(level)) else: test_node.get_logger().error('Failed to get logger level via logger service !') - ret, child_level = test_node.get_logger_level_on_remote_node(child_logger_name) + child_results = test_node.get_logger_level_on_remote_node(child_logger_name) + ret, child_level = child_results[0] if ret: test_node.get_logger().info('Current child logger level: ' + str(child_level)) else: test_node.get_logger().error('Failed to get child logger level via logger service !') -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: # Check for --service-only flag before ROS 2 consumes the arguments parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--service-only', action='store_true', default=False) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index 934c11b88..a4d8f0ad7 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from rcl_interfaces.msg import SetParametersResult import rclpy @@ -40,8 +43,8 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] - ) -> list[Parameter]: # type: ignore[type-arg] + parameter_list: List[Parameter], # type: ignore[type-arg] + ) -> List[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -51,7 +54,7 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] + parameter_list: List[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -66,7 +69,7 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] + parameter_list: List[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name == 'param1': @@ -79,7 +82,7 @@ def post_set_parameter_callback( self.add_post_set_parameters_callback(post_set_parameter_callback) -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): node = SetParametersCallback() diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py index e047f445b..d54e2a7ad 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py @@ -13,13 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from example_interfaces.srv import AddTwoInts import rclpy from rclpy.executors import ExternalShutdownException -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') @@ -32,8 +35,9 @@ def main(args: list[str] | None = None) -> None: req.b = 3 future = cli.call_async(req) rclpy.spin_until_future_complete(node, future) - if future.result() is not None: - res_sum = future.result().sum # type: ignore[union-attr] + result = future.result() + if result is not None: + res_sum = result.sum node.get_logger().info('Result of add_two_ints: %d' % res_sum) else: node.get_logger().error('Exception while calling service: %r' % future.exception()) diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py index 7804286e1..4f3d7dc05 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py @@ -13,13 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from example_interfaces.srv import AddTwoInts import rclpy from rclpy.executors import ExternalShutdownException -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') @@ -38,8 +41,9 @@ def main(args: list[str] | None = None) -> None: while rclpy.ok(): rclpy.spin_once(node) if future.done(): - if future.result() is not None: - res_sum = future.result().sum # type: ignore[union-attr] + result = future.result() + if result is not None: + res_sum = result.sum logger.info('Result of add_two_ints: %d' % res_sum) else: logger.error('Exception while calling service: %r' % future.exception()) diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 59985fab2..25a8f48ac 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2022 Open Source Robotics Foundation, Inc. +# Copyright 2023 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from example_interfaces.srv import AddTwoInts from rcl_interfaces.msg import SetParametersResult @@ -73,7 +76,7 @@ # ros2 topic echo /add_two_ints/_service_event def check_parameter( - parameter_list: list[Parameter], parameter_name: str # type: ignore[type-arg] + parameter_list: List[Parameter], parameter_name: str # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -97,12 +100,12 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: List[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: List[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -160,12 +163,12 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: List[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: List[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': @@ -204,7 +207,7 @@ def add_two_ints_callback( return response -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): service_node = IntrospectionServiceNode() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker.py b/demo_nodes_py/demo_nodes_py/topics/talker.py index a596ab8f8..9d20195df 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker.py @@ -13,12 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List +from typing import Union + from example_interfaces.msg import String import rclpy from rclpy.executors import ExternalShutdownException from rclpy.node import Node -from rclpy.timer import Timer class Talker(Node): @@ -28,7 +30,7 @@ def __init__(self) -> None: self.i: int = 0 self.pub = self.create_publisher(String, 'chatter', 10) timer_period = 1.0 - self.tmr: Timer = self.create_timer(timer_period, self.timer_callback) + self.tmr = self.create_timer(timer_period, self.timer_callback) def timer_callback(self) -> None: msg = String() @@ -38,7 +40,7 @@ def timer_callback(self) -> None: self.pub.publish(msg) -def main(args: list[str] | None = None) -> None: +def main(args: Union[List[str], None] = None) -> None: try: with rclpy.init(args=args): node = Talker() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py index 255d813a4..3d14dd59a 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py @@ -15,6 +15,7 @@ import argparse import sys +from typing import List from example_interfaces.msg import String @@ -24,7 +25,6 @@ from rclpy.qos import qos_profile_sensor_data from rclpy.qos import QoSProfile from rclpy.qos import QoSReliabilityPolicy -from rclpy.timer import Timer from rclpy.utilities import remove_ros_args @@ -40,7 +40,7 @@ def __init__(self, qos_profile: QoSProfile) -> None: self.pub = self.create_publisher(String, 'chatter', qos_profile) timer_period = 1.0 - self.tmr: Timer = self.create_timer(timer_period, self.timer_callback) + self.tmr = self.create_timer(timer_period, self.timer_callback) def timer_callback(self) -> None: msg = String() @@ -50,7 +50,7 @@ def timer_callback(self) -> None: self.pub.publish(msg) -def main(argv: list[str] = sys.argv[1:]) -> None: +def main(argv: List[str] = sys.argv[1:]) -> None: parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '--reliable', dest='reliable', action='store_true', diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py index 9e7660fd2..afc6a332c 100644 --- a/demo_nodes_py/test/test_mypy.py +++ b/demo_nodes_py/test/test_mypy.py @@ -17,5 +17,5 @@ def test_mypy() -> None: - rc = main(argv=[]) + rc = main() assert rc == 0, 'Found code style errors / warnings' From c8c2935bfb2e56cdb9b40c1fba83c3824999c9e6 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Sun, 22 Mar 2026 00:40:10 +0900 Subject: [PATCH 03/11] revert formatting, fix python 3.9 compatibility, and PEP 585 typing Signed-off-by: Jonathan Setiawan --- .../events/matched_event_detect.py | 7 +++--- .../logging/use_logger_service.py | 17 +++++++------- .../parameters/set_parameters_callback.py | 11 +++++----- .../services/add_two_ints_client.py | 3 +-- .../services/add_two_ints_client_async.py | 3 +-- .../demo_nodes_py/services/introspection.py | 22 +++++++++---------- .../topics/listener_serialized.py | 11 +++++----- demo_nodes_py/demo_nodes_py/topics/talker.py | 5 ++--- .../demo_nodes_py/topics/talker_qos.py | 3 +-- demo_nodes_py/test/test_mypy.py | 2 +- 10 files changed, 38 insertions(+), 46 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py index 69cb81dae..d72c00f45 100644 --- a/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py +++ b/demo_nodes_py/demo_nodes_py/events/matched_event_detect.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from example_interfaces.msg import String @@ -98,7 +97,7 @@ class MultiSubNode(Node): def __init__(self, topic_name: str): super().__init__('multi_sub_node') - self.__subs: List[Subscription[String]] = [] + self.__subs: list[Subscription[String]] = [] self.__topic_name = topic_name def create_one_sub(self) -> Subscription[String]: @@ -119,7 +118,7 @@ class MultiPubNode(Node): def __init__(self, topic_name: str): super().__init__('multi_pub_node') - self.__pubs: List[Publisher[String]] = [] + self.__pubs: list[Publisher[String]] = [] self.__topic_name = topic_name def create_one_pub(self) -> Publisher[String]: @@ -136,7 +135,7 @@ def destroy_one_pub(self, pub: Publisher[String]) -> None: self.destroy_publisher(pub) -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): topic_name_for_detect_pub_matched_event = 'pub_topic_matched_event_detect' diff --git a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py index 7ec1c05eb..4d331e6bf 100644 --- a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py +++ b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py @@ -12,12 +12,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import argparse import threading import time -from typing import List from typing import Literal -from typing import Tuple from typing import Union from example_interfaces.msg import String @@ -107,7 +106,7 @@ def set_logger_level_on_remote_node( set_logger_level = LoggerLevel() set_logger_level.name = logger_name if logger_name else self._remote_node_name set_logger_level.level = logger_level - request.levels = [set_logger_level] + request.levels.append(set_logger_level) future = self._logger_set_client.call_async(request) rclpy.spin_until_future_complete(self, future) @@ -125,9 +124,9 @@ def set_logger_level_on_remote_node( def get_logger_level_on_remote_node( self, logger_name: str = '', - ) -> List[Union[Tuple[Literal[False], None], Tuple[Literal[True], int]]]: + ) -> Union[tuple[Literal[False], None], tuple[Literal[True], int]]: if not self.logger_get_client.service_is_ready(): - return [(False, None)] + return False, None request = GetLoggerLevels.Request() name = logger_name if logger_name else self._remote_node_name @@ -140,25 +139,25 @@ def get_logger_level_on_remote_node( if not ret_results: return [(False, None)] - return [(True, ret_results.levels[0].level)] + return True, ret_results.levels[0].level def get_logger_level_func(test_node: TestNode, child_logger_name: str) -> None: results = test_node.get_logger_level_on_remote_node() - ret, level = results[0] + ret, level = results if ret: test_node.get_logger().info('Current logger level: ' + str(level)) else: test_node.get_logger().error('Failed to get logger level via logger service !') child_results = test_node.get_logger_level_on_remote_node(child_logger_name) - ret, child_level = child_results[0] + ret, child_level = child_results if ret: test_node.get_logger().info('Current child logger level: ' + str(child_level)) else: test_node.get_logger().error('Failed to get child logger level via logger service !') -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: # Check for --service-only flag before ROS 2 consumes the arguments parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--service-only', action='store_true', default=False) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index a4d8f0ad7..64ca79ccc 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from rcl_interfaces.msg import SetParametersResult @@ -43,8 +42,8 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: List[Parameter], # type: ignore[type-arg] - ) -> List[Parameter]: # type: ignore[type-arg] + parameter_list: list[Parameter], + ) -> list[Parameter]: modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -54,7 +53,7 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: List[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -69,7 +68,7 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: List[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], ) -> None: for param in parameter_list: if param.name == 'param1': @@ -82,7 +81,7 @@ def post_set_parameter_callback( self.add_post_set_parameters_callback(post_set_parameter_callback) -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): node = SetParametersCallback() diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py index d54e2a7ad..34edabd07 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from example_interfaces.srv import AddTwoInts @@ -22,7 +21,7 @@ from rclpy.executors import ExternalShutdownException -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') diff --git a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py index 4f3d7dc05..6d88191b7 100644 --- a/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py +++ b/demo_nodes_py/demo_nodes_py/services/add_two_ints_client_async.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from example_interfaces.srv import AddTwoInts @@ -22,7 +21,7 @@ from rclpy.executors import ExternalShutdownException -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): node = rclpy.create_node('add_two_ints_client') diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 25a8f48ac..59a9e72a8 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from example_interfaces.srv import AddTwoInts @@ -76,7 +75,7 @@ # ros2 topic echo /add_two_ints/_service_event def check_parameter( - parameter_list: List[Parameter], parameter_name: str # type: ignore[type-arg] + parameter_list: list[Parameter], parameter_name: str ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -100,12 +99,12 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: List[Parameter], # type: ignore[type-arg] + self, parameter_list: list[Parameter], ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: List[Parameter], # type: ignore[type-arg] + self, parameter_list: list[Parameter], ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -132,8 +131,8 @@ def __init__(self) -> None: self.add_post_set_parameters_callback(self.on_post_set_parameters_callback) self.declare_parameter('client_configure_introspection', 'disabled') - self.timer: Timer = self.create_timer(0.5, self.timer_callback) - self.future: Future[AddTwoInts.Response] | None = None + self.timer = self.create_timer(0.5, self.timer_callback) + self.future: Union[Future[AddTwoInts.Response], None] = None def timer_callback(self) -> None: if not self.cli.service_is_ready(): @@ -151,8 +150,9 @@ def timer_callback(self) -> None: if not self.future.done(): return - if self.future.result() is not None: - res_sum = self.future.result().sum # type: ignore[union-attr] + result = self.future.result() + if result is not None: + res_sum = result.sum self.get_logger().info('Result of add_two_ints: %d' % res_sum) else: self.get_logger().error('Exception calling service: %r' % self.future.exception()) @@ -163,12 +163,12 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: List[Parameter], # type: ignore[type-arg] + self, parameter_list: list[Parameter], ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: List[Parameter], # type: ignore[type-arg] + self, parameter_list: list[Parameter], ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': @@ -207,7 +207,7 @@ def add_two_ints_callback( return response -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): service_node = IntrospectionServiceNode() diff --git a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py index cdf45f3ff..b929be023 100644 --- a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py +++ b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py @@ -13,15 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ctypes import Union + from example_interfaces.msg import String +from typing import Union import rclpy from rclpy.executors import ExternalShutdownException from rclpy.node import Node - class SerializedSubscriber(Node): - def __init__(self) -> None: super().__init__('serialized_subscriber') self.subscription = self.create_subscription( @@ -34,10 +35,9 @@ def __init__(self) -> None: # not example_interfaces.msg.String def listener_callback(self, msg: bytes) -> None: - self.get_logger().info('I heard: "%r"' % msg) + self.get_logger().info('I heard: "%s"' % msg) - -def main(args: list[str] | None = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): serialized_subscriber = SerializedSubscriber() @@ -46,6 +46,5 @@ def main(args: list[str] | None = None) -> None: except (KeyboardInterrupt, ExternalShutdownException): pass - if __name__ == '__main__': main() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker.py b/demo_nodes_py/demo_nodes_py/topics/talker.py index 9d20195df..4f16db25b 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from typing import Union from example_interfaces.msg import String @@ -27,7 +26,7 @@ class Talker(Node): def __init__(self) -> None: super().__init__('talker') - self.i: int = 0 + self.i = 0 self.pub = self.create_publisher(String, 'chatter', 10) timer_period = 1.0 self.tmr = self.create_timer(timer_period, self.timer_callback) @@ -40,7 +39,7 @@ def timer_callback(self) -> None: self.pub.publish(msg) -def main(args: Union[List[str], None] = None) -> None: +def main(args: Union[list[str], None] = None) -> None: try: with rclpy.init(args=args): node = Talker() diff --git a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py index 3d14dd59a..644b51b28 100644 --- a/demo_nodes_py/demo_nodes_py/topics/talker_qos.py +++ b/demo_nodes_py/demo_nodes_py/topics/talker_qos.py @@ -15,7 +15,6 @@ import argparse import sys -from typing import List from example_interfaces.msg import String @@ -50,7 +49,7 @@ def timer_callback(self) -> None: self.pub.publish(msg) -def main(argv: List[str] = sys.argv[1:]) -> None: +def main(argv: list[str] = sys.argv[1:]) -> None: parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '--reliable', dest='reliable', action='store_true', diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py index afc6a332c..8b1a09e35 100644 --- a/demo_nodes_py/test/test_mypy.py +++ b/demo_nodes_py/test/test_mypy.py @@ -17,5 +17,5 @@ def test_mypy() -> None: - rc = main() + rc = main(argv=['--ament-strict']) assert rc == 0, 'Found code style errors / warnings' From 40082a861306dcec51b6c65f76b2abe003bccc79 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Sun, 22 Mar 2026 10:21:50 +0900 Subject: [PATCH 04/11] restore EOF newline, %r byte-safe logging, and attr-defined ignore Signed-off-by: Jonathan Setiawan --- .../demo_nodes_py/logging/use_logger_service.py | 4 ++-- demo_nodes_py/demo_nodes_py/services/introspection.py | 3 +-- .../demo_nodes_py/topics/listener_serialized.py | 9 ++++++--- demo_nodes_py/test/test_mypy.py | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py index 4d331e6bf..dbe7482b2 100644 --- a/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py +++ b/demo_nodes_py/demo_nodes_py/logging/use_logger_service.py @@ -106,7 +106,7 @@ def set_logger_level_on_remote_node( set_logger_level = LoggerLevel() set_logger_level.name = logger_name if logger_name else self._remote_node_name set_logger_level.level = logger_level - request.levels.append(set_logger_level) + request.levels.append(set_logger_level) # type: ignore[attr-defined] future = self._logger_set_client.call_async(request) rclpy.spin_until_future_complete(self, future) @@ -137,7 +137,7 @@ def get_logger_level_on_remote_node( ret_results = future.result() if not ret_results: - return [(False, None)] + return False, None return True, ret_results.levels[0].level diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 59a9e72a8..252c8fdbf 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -26,8 +26,6 @@ from rclpy.qos import qos_profile_system_default from rclpy.service_introspection import ServiceIntrospectionState from rclpy.task import Future -from rclpy.timer import Timer - # This demo program shows how to configure client and service introspection # on the fly, by hooking it up to a parameter. This program consists of both @@ -74,6 +72,7 @@ # In either case, service introspection data can be seen by running: # ros2 topic echo /add_two_ints/_service_event + def check_parameter( parameter_list: list[Parameter], parameter_name: str ) -> SetParametersResult: diff --git a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py index b929be023..09a551cbd 100644 --- a/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py +++ b/demo_nodes_py/demo_nodes_py/topics/listener_serialized.py @@ -13,16 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ctypes import Union +from typing import Union from example_interfaces.msg import String -from typing import Union import rclpy from rclpy.executors import ExternalShutdownException from rclpy.node import Node + class SerializedSubscriber(Node): + def __init__(self) -> None: super().__init__('serialized_subscriber') self.subscription = self.create_subscription( @@ -35,7 +36,8 @@ def __init__(self) -> None: # not example_interfaces.msg.String def listener_callback(self, msg: bytes) -> None: - self.get_logger().info('I heard: "%s"' % msg) + self.get_logger().info('I heard: "%r"' % msg) + def main(args: Union[list[str], None] = None) -> None: try: @@ -46,5 +48,6 @@ def main(args: Union[list[str], None] = None) -> None: except (KeyboardInterrupt, ExternalShutdownException): pass + if __name__ == '__main__': main() diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py index 8b1a09e35..afc6a332c 100644 --- a/demo_nodes_py/test/test_mypy.py +++ b/demo_nodes_py/test/test_mypy.py @@ -17,5 +17,5 @@ def test_mypy() -> None: - rc = main(argv=['--ament-strict']) + rc = main() assert rc == 0, 'Found code style errors / warnings' From 41cfe49502ebc32aa9e48c519a23349738cd8912 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Wed, 8 Apr 2026 11:47:13 +0900 Subject: [PATCH 05/11] pass --ament-strict as list to avoid crash in test_mypy Signed-off-by: Jonathan Setiawan --- demo_nodes_py/test/test_mypy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py index afc6a332c..8b1a09e35 100644 --- a/demo_nodes_py/test/test_mypy.py +++ b/demo_nodes_py/test/test_mypy.py @@ -17,5 +17,5 @@ def test_mypy() -> None: - rc = main() + rc = main(argv=['--ament-strict']) assert rc == 0, 'Found code style errors / warnings' From f917b2ef86986356fdcb67a1d14959138e01ad69 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Wed, 15 Apr 2026 04:02:08 +0900 Subject: [PATCH 06/11] restore [type-arg] ignores for list[Parameter] Signed-off-by: Jonathan Setiawan --- .../parameters/set_parameters_callback.py | 8 ++++---- demo_nodes_py/demo_nodes_py/services/introspection.py | 10 +++++----- demo_nodes_py/test/test_mypy.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index 64ca79ccc..f280f0659 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -42,8 +42,8 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: list[Parameter], - ) -> list[Parameter]: + parameter_list: list[Parameter], # type: ignore[type-arg] + ) -> list[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -53,7 +53,7 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: list[Parameter], + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -68,7 +68,7 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: list[Parameter], + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name == 'param1': diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 252c8fdbf..796b804fd 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -74,7 +74,7 @@ def check_parameter( - parameter_list: list[Parameter], parameter_name: str + parameter_list: list[Parameter], parameter_name: str # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -98,12 +98,12 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], + self, parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], + self, parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -162,12 +162,12 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], + self, parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], + self, parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': diff --git a/demo_nodes_py/test/test_mypy.py b/demo_nodes_py/test/test_mypy.py index 8b1a09e35..afc6a332c 100644 --- a/demo_nodes_py/test/test_mypy.py +++ b/demo_nodes_py/test/test_mypy.py @@ -17,5 +17,5 @@ def test_mypy() -> None: - rc = main(argv=['--ament-strict']) + rc = main() assert rc == 0, 'Found code style errors / warnings' From 4d1d6a40b5ea2dcc3acf93847d761148f5c73f8c Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Thu, 16 Apr 2026 02:56:01 +0900 Subject: [PATCH 07/11] wrap lines for list[Parameter] ignores Signed-off-by: Jonathan Setiawan --- .../parameters/set_parameters_callback.py | 11 +++++++---- .../demo_nodes_py/services/introspection.py | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index f280f0659..4d157a023 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union +from typing import Any, Union from rcl_interfaces.msg import SetParametersResult @@ -42,7 +42,8 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] + parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> list[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: @@ -53,7 +54,8 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] + parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -68,7 +70,8 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: list[Parameter], # type: ignore[type-arg] + parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name == 'param1': diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 796b804fd..0c97c9ccb 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union +from typing import Any, Union from example_interfaces.srv import AddTwoInts from rcl_interfaces.msg import SetParametersResult @@ -74,7 +74,9 @@ def check_parameter( - parameter_list: list[Parameter], parameter_name: str # type: ignore[type-arg] + parameter_list: \ + list[Parameter], # type: ignore[type-arg] + parameter_name: str ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -98,12 +100,14 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -162,12 +166,14 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: list[Parameter], # type: ignore[type-arg] + self, parameter_list: \ + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': From afa672fbd2e3f8f8db17d667de3ec2f41289abfe Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Thu, 16 Apr 2026 18:35:28 +0900 Subject: [PATCH 08/11] use ParamList type alias to fix flake8 indentation errors Signed-off-by: Jonathan Setiawan --- .../parameters/set_parameters_callback.py | 15 +++++++------- .../demo_nodes_py/services/introspection.py | 20 ++++++++----------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index 4d157a023..fd0ac8532 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Union +from typing import Union from rcl_interfaces.msg import SetParametersResult @@ -22,6 +22,8 @@ from rclpy.node import Node from rclpy.parameter import Parameter +ParamList = list[Parameter] # type: ignore[type-arg] + # Example usage: changing param1 successfully will result in setting of param2. # ros2 service call /set_parameters_callback/set_parameters rcl_interfaces/srv/SetParameters @@ -42,9 +44,8 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: \ - list[Parameter], # type: ignore[type-arg] - ) -> list[Parameter]: # type: ignore[type-arg] + parameter_list: ParamList, + ) -> ParamList: modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -54,8 +55,7 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: \ - list[Parameter], # type: ignore[type-arg] + parameter_list: ParamList, ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -70,8 +70,7 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: \ - list[Parameter], # type: ignore[type-arg] + parameter_list: ParamList, ) -> None: for param in parameter_list: if param.name == 'param1': diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 0c97c9ccb..6ce94a341 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Union +from typing import Union from example_interfaces.srv import AddTwoInts from rcl_interfaces.msg import SetParametersResult @@ -27,6 +27,8 @@ from rclpy.service_introspection import ServiceIntrospectionState from rclpy.task import Future +ParamList = list[Parameter] # type: ignore[type-arg] + # This demo program shows how to configure client and service introspection # on the fly, by hooking it up to a parameter. This program consists of both # a client node (IntrospectionClientNode) and a service node (IntrospectionServiceNode). @@ -74,9 +76,7 @@ def check_parameter( - parameter_list: \ - list[Parameter], # type: ignore[type-arg] - parameter_name: str + parameter_list: ParamList, parameter_name: str ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -100,14 +100,12 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: \ - list[Parameter], # type: ignore[type-arg] + self, parameter_list: ParamList, ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: \ - list[Parameter], # type: ignore[type-arg] + self, parameter_list: ParamList, ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -166,14 +164,12 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: \ - list[Parameter], # type: ignore[type-arg] + self, parameter_list: ParamList, ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: \ - list[Parameter], # type: ignore[type-arg] + self, parameter_list: ParamList, ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': From 873fa6b157a609c2a51090bda942ea9d835f4c17 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Fri, 17 Apr 2026 06:19:09 +0900 Subject: [PATCH 09/11] remove formatting tags and re-apply line wraps Signed-off-by: Jonathan Setiawan --- .../parameters/set_parameters_callback.py | 12 ++++++----- .../demo_nodes_py/services/introspection.py | 21 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index fd0ac8532..0c7c149e4 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -22,7 +22,6 @@ from rclpy.node import Node from rclpy.parameter import Parameter -ParamList = list[Parameter] # type: ignore[type-arg] # Example usage: changing param1 successfully will result in setting of param2. @@ -44,8 +43,9 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: ParamList, - ) -> ParamList: + parameter_list: + list[Parameter], # type: ignore[type-arg] + ) -> list[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: if param.name == 'param1': @@ -55,7 +55,8 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: ParamList, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -70,7 +71,8 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: ParamList, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name == 'param1': diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 6ce94a341..8380c8a19 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -27,7 +27,6 @@ from rclpy.service_introspection import ServiceIntrospectionState from rclpy.task import Future -ParamList = list[Parameter] # type: ignore[type-arg] # This demo program shows how to configure client and service introspection # on the fly, by hooking it up to a parameter. This program consists of both @@ -76,7 +75,9 @@ def check_parameter( - parameter_list: ParamList, parameter_name: str + parameter_list: + list[Parameter], # type: ignore[type-arg] + parameter_name: str ) -> SetParametersResult: result = SetParametersResult() result.successful = True @@ -100,12 +101,16 @@ def check_parameter( class IntrospectionClientNode(Node): def on_set_parameters_callback( - self, parameter_list: ParamList, + self, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: ParamList, + self, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -164,12 +169,16 @@ def timer_callback(self) -> None: class IntrospectionServiceNode(Node): def on_set_parameters_callback( - self, parameter_list: ParamList, + self, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( - self, parameter_list: ParamList, + self, + parameter_list: + list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': From 6be6865b0b2e897d6bbec9d384f20aabcb1bd67e Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Fri, 17 Apr 2026 08:23:39 +0900 Subject: [PATCH 10/11] fix hanging indentation and format type ignores Signed-off-by: Jonathan Setiawan --- .../parameters/set_parameters_callback.py | 9 +++------ .../demo_nodes_py/services/introspection.py | 15 +++++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index 0c7c149e4..e3aa2c6dd 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -43,8 +43,7 @@ def __init__(self) -> None: # setting another parameter from the callback is possible # we expect the callback to be called for param2 def pre_set_parameter_callback( - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> list[Parameter]: # type: ignore[type-arg] modified_parameters = parameter_list.copy() for param in parameter_list: @@ -55,8 +54,7 @@ def pre_set_parameter_callback( # validation callback def on_set_parameter_callback( - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: result = SetParametersResult() for param in parameter_list: @@ -71,8 +69,7 @@ def on_set_parameter_callback( # can change internally tracked class attributes def post_set_parameter_callback( - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name == 'param1': diff --git a/demo_nodes_py/demo_nodes_py/services/introspection.py b/demo_nodes_py/demo_nodes_py/services/introspection.py index 8380c8a19..a7ddb2321 100644 --- a/demo_nodes_py/demo_nodes_py/services/introspection.py +++ b/demo_nodes_py/demo_nodes_py/services/introspection.py @@ -75,8 +75,7 @@ def check_parameter( - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] parameter_name: str ) -> SetParametersResult: result = SetParametersResult() @@ -102,15 +101,13 @@ class IntrospectionClientNode(Node): def on_set_parameters_callback( self, - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'client_configure_introspection') def on_post_set_parameters_callback( self, - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'client_configure_introspection': @@ -170,15 +167,13 @@ class IntrospectionServiceNode(Node): def on_set_parameters_callback( self, - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> SetParametersResult: return check_parameter(parameter_list, 'service_configure_introspection') def on_post_set_parameters_callback( self, - parameter_list: - list[Parameter], # type: ignore[type-arg] + parameter_list: list[Parameter], # type: ignore[type-arg] ) -> None: for param in parameter_list: if param.name != 'service_configure_introspection': From 6fc60a66c937925fe2da8450fe95b4576d515b38 Mon Sep 17 00:00:00 2001 From: Jonathan Setiawan Date: Fri, 17 Apr 2026 09:41:03 +0900 Subject: [PATCH 11/11] fix spacing Signed-off-by: Jonathan Setiawan --- .../demo_nodes_py/parameters/set_parameters_callback.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py index e3aa2c6dd..8e83bef37 100644 --- a/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py +++ b/demo_nodes_py/demo_nodes_py/parameters/set_parameters_callback.py @@ -23,13 +23,14 @@ from rclpy.parameter import Parameter - # Example usage: changing param1 successfully will result in setting of param2. # ros2 service call /set_parameters_callback/set_parameters rcl_interfaces/srv/SetParameters # "{parameters: [{name: "param1", value: {type: 3, double_value: 1.0}}]}" # node for demonstrating correct usage of pre_set, on_set # and post_set parameter callbacks + + class SetParametersCallback(Node): def __init__(self) -> None: