diff --git a/rclpy/rclpy/action/__init__.py b/rclpy/rclpy/action/__init__.py index a0dd9bf87..4ca87092d 100644 --- a/rclpy/rclpy/action/__init__.py +++ b/rclpy/rclpy/action/__init__.py @@ -13,6 +13,11 @@ # limitations under the License. from .client import ActionClient as ActionClient # noqa: F401 +# The following graph functions are deprecated. +# Use the corresponding methods on the Node class instead: +# - node.get_action_client_names_and_types_by_node() +# - node.get_action_server_names_and_types_by_node() +# - node.get_action_names_and_types() from .graph import ( # noqa: F401 get_action_client_names_and_types_by_node as get_action_client_names_and_types_by_node ) diff --git a/rclpy/rclpy/action/graph.py b/rclpy/rclpy/action/graph.py index 362e48b97..72d25a021 100644 --- a/rclpy/rclpy/action/graph.py +++ b/rclpy/rclpy/action/graph.py @@ -14,7 +14,9 @@ from typing import List from typing import Tuple +import warnings +from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy from rclpy.node import Node @@ -26,6 +28,9 @@ def get_action_client_names_and_types_by_node( """ Get a list of action names and types for action clients associated with a node. + .. deprecated:: (upcoming release) + Use :meth:`rclpy.node.Node.get_action_client_names_and_types_by_node` instead. + :param node: The node used for discovery. :param remote_node_name: The name of a remote node to get action clients for. :param node_namespace: Namespace of the remote node. @@ -33,9 +38,15 @@ def get_action_client_names_and_types_by_node( The first element of each tuple is the action name and the second element is a list of action types. """ + warnings.warn( + 'rclpy.action.get_action_client_names_and_types_by_node() is deprecated. ' + 'Use rclpy.node.Node.get_action_client_names_and_types_by_node() instead.', + DeprecationWarning, + stacklevel=2 + ) with node.handle: - return node.handle.get_action_client_names_and_types_by_node( - remote_node_name, remote_node_namespace) + return _rclpy.rclpy_get_action_client_names_and_types_by_node( + node.handle, remote_node_name, remote_node_namespace) def get_action_server_names_and_types_by_node( @@ -46,6 +57,9 @@ def get_action_server_names_and_types_by_node( """ Get a list of action names and types for action servers associated with a node. + .. deprecated:: (upcoming release) + Use :meth:`rclpy.node.Node.get_action_server_names_and_types_by_node` instead. + :param node: The node used for discovery. :param remote_node_name: The name of a remote node to get action servers for. :param node_namespace: Namespace of the remote node. @@ -53,19 +67,34 @@ def get_action_server_names_and_types_by_node( The first element of each tuple is the action name and the second element is a list of action types. """ + warnings.warn( + 'rclpy.action.get_action_server_names_and_types_by_node() is deprecated. ' + 'Use rclpy.node.Node.get_action_server_names_and_types_by_node() instead.', + DeprecationWarning, + stacklevel=2 + ) with node.handle: - return node.handle.get_action_server_names_and_types_by_node( - remote_node_name, remote_node_namespace) + return _rclpy.rclpy_get_action_server_names_and_types_by_node( + node.handle, remote_node_name, remote_node_namespace) def get_action_names_and_types(node: Node) -> List[Tuple[str, List[str]]]: """ Get a list of action names and types. + .. deprecated:: (upcoming release) + Use :meth:`rclpy.node.Node.get_action_names_and_types` instead. + :param node: The node used for discovery. :return: List of action names and types in the ROS graph as tuples. The first element of each tuple is the action name and the second element is a list of action types. """ + warnings.warn( + 'rclpy.action.get_action_names_and_types() is deprecated. ' + 'Use rclpy.node.Node.get_action_names_and_types() instead.', + DeprecationWarning, + stacklevel=2 + ) with node.handle: - return node.handle.get_action_names_and_types() + return _rclpy.rclpy_get_action_names_and_types(node.handle) diff --git a/rclpy/rclpy/impl/_rclpy_pybind11.pyi b/rclpy/rclpy/impl/_rclpy_pybind11.pyi index 09108f987..fda7a926b 100644 --- a/rclpy/rclpy/impl/_rclpy_pybind11.pyi +++ b/rclpy/rclpy/impl/_rclpy_pybind11.pyi @@ -801,6 +801,22 @@ def rclpy_get_client_names_and_types_by_node(node: Node, node_name: str, node_na """Get service names and types for which a remote node has servers.""" +def rclpy_get_action_client_names_and_types_by_node(node: Node, node_name: str, + node_namespace: str + ) -> list[tuple[str, list[str]]]: + """Get action client names and types by node.""" + + +def rclpy_get_action_server_names_and_types_by_node(node: Node, node_name: str, + node_namespace: str + ) -> list[tuple[str, list[str]]]: + """Get action server names and types by node.""" + + +def rclpy_get_action_names_and_types(node: Node) -> list[tuple[str, list[str]]]: + """Get all action names and types in the ROS graph.""" + + def rclpy_serialize(pymsg: Msg, py_msg_type: type[Msg]) -> bytes: """Serialize a ROS message.""" diff --git a/rclpy/rclpy/node.py b/rclpy/rclpy/node.py index 4c576776e..74ae782dc 100644 --- a/rclpy/rclpy/node.py +++ b/rclpy/rclpy/node.py @@ -2142,6 +2142,57 @@ def get_client_names_and_types_by_node( return _rclpy.rclpy_get_client_names_and_types_by_node( self.handle, node_name, node_namespace) + def get_action_client_names_and_types_by_node( + self, + node_name: str, + node_namespace: str + ) -> List[Tuple[str, List[str]]]: + """ + Get a list of action names and types for action clients associated with a remote node. + + :param node_name: Name of a remote node to get action clients for. + :param node_namespace: Namespace of the remote node. + :return: List of tuples. + The first element of each tuple is the action name and the second element is a list of + action types. + :raise NodeNameNonExistentError: If the node wasn't found. + :raise RuntimeError: Unexpected failure. + """ + with self.handle: + return _rclpy.rclpy_get_action_client_names_and_types_by_node( + self.handle, node_name, node_namespace) + + def get_action_server_names_and_types_by_node( + self, + node_name: str, + node_namespace: str + ) -> List[Tuple[str, List[str]]]: + """ + Get a list of action names and types for action servers associated with a remote node. + + :param node_name: Name of a remote node to get action servers for. + :param node_namespace: Namespace of the remote node. + :return: List of tuples. + The first element of each tuple is the action name and the second element is a list of + action types. + :raise NodeNameNonExistentError: If the node wasn't found. + :raise RuntimeError: Unexpected failure. + """ + with self.handle: + return _rclpy.rclpy_get_action_server_names_and_types_by_node( + self.handle, node_name, node_namespace) + + def get_action_names_and_types(self) -> List[Tuple[str, List[str]]]: + """ + Get a list of action names and types in the ROS graph. + + :return: List of tuples. + The first element of each tuple is the action name and the second element is a list of + action types. + """ + with self.handle: + return _rclpy.rclpy_get_action_names_and_types(self.handle) + def get_topic_names_and_types(self, no_demangle: bool = False) -> List[Tuple[str, List[str]]]: """ Get a list of discovered topic names and types. diff --git a/rclpy/src/rclpy/_rclpy_pybind11.cpp b/rclpy/src/rclpy/_rclpy_pybind11.cpp index 2e250de96..b48ff774b 100644 --- a/rclpy/src/rclpy/_rclpy_pybind11.cpp +++ b/rclpy/src/rclpy/_rclpy_pybind11.cpp @@ -221,7 +221,18 @@ PYBIND11_MODULE(_rclpy_pybind11, m) { "rclpy_get_client_names_and_types_by_node", &rclpy::graph_get_client_names_and_types_by_node, "Get service names and types for which a remote node has clients."); - + m.def( + "rclpy_get_action_client_names_and_types_by_node", + &rclpy::graph_get_action_client_names_and_types_by_node, + "Get action client names and types by node."); + m.def( + "rclpy_get_action_server_names_and_types_by_node", + &rclpy::graph_get_action_server_names_and_types_by_node, + "Get action server names and types by node."); + m.def( + "rclpy_get_action_names_and_types", + &rclpy::graph_get_action_names_and_types, + "Get all action names and types in the ROS graph."); m.def( "rclpy_serialize", &rclpy::serialize, "Serialize a ROS message."); diff --git a/rclpy/src/rclpy/graph.cpp b/rclpy/src/rclpy/graph.cpp index 16963731d..7dae5fa29 100644 --- a/rclpy/src/rclpy/graph.cpp +++ b/rclpy/src/rclpy/graph.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -343,4 +344,94 @@ graph_get_servers_info_by_service( rcl_get_servers_info_by_service); } +py::list +graph_get_action_client_names_and_types_by_node( + Node & node, std::string node_name, std::string node_namespace) +{ + rcl_names_and_types_t action_names_and_types = rcl_get_zero_initialized_names_and_types(); + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcl_ret_t ret = rcl_action_get_client_names_and_types_by_node( + node.rcl_ptr(), &allocator, node_name.c_str(), node_namespace.c_str(), + &action_names_and_types); + if (RCL_RET_OK != ret) { + if (RCL_RET_NODE_NAME_NON_EXISTENT == ret) { + throw NodeNameNonExistentError( + "cannot get action client names and types for nonexistent node"); + } + throw RCLError("failed to get action client names and types"); + } + RCPPUTILS_SCOPE_EXIT( + { + ret = rcl_names_and_types_fini(&action_names_and_types); + if (RCL_RET_OK != ret) { + RCUTILS_SAFE_FWRITE_TO_STDERR( + "[rclpy|" RCUTILS_STRINGIFY(__FILE__) ":" RCUTILS_STRINGIFY(__LINE__) "]: " + "failed to fini action client names and types during error handling: "); + RCUTILS_SAFE_FWRITE_TO_STDERR(rcl_get_error_string().str); + RCUTILS_SAFE_FWRITE_TO_STDERR("\n"); + rcl_reset_error(); + } + }); + + return convert_to_py_names_and_types(&action_names_and_types); +} + +py::list +graph_get_action_server_names_and_types_by_node( + Node & node, std::string node_name, std::string node_namespace) +{ + rcl_names_and_types_t action_names_and_types = rcl_get_zero_initialized_names_and_types(); + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcl_ret_t ret = rcl_action_get_server_names_and_types_by_node( + node.rcl_ptr(), &allocator, node_name.c_str(), node_namespace.c_str(), + &action_names_and_types); + if (RCL_RET_OK != ret) { + if (RCL_RET_NODE_NAME_NON_EXISTENT == ret) { + throw NodeNameNonExistentError( + "cannot get action server names and types for nonexistent node"); + } + throw RCLError("failed to get action server names and types"); + } + RCPPUTILS_SCOPE_EXIT( + { + ret = rcl_names_and_types_fini(&action_names_and_types); + if (RCL_RET_OK != ret) { + RCUTILS_SAFE_FWRITE_TO_STDERR( + "[rclpy|" RCUTILS_STRINGIFY(__FILE__) ":" RCUTILS_STRINGIFY(__LINE__) "]: " + "failed to fini action server names and types during error handling: "); + RCUTILS_SAFE_FWRITE_TO_STDERR(rcl_get_error_string().str); + RCUTILS_SAFE_FWRITE_TO_STDERR("\n"); + rcl_reset_error(); + } + }); + + return convert_to_py_names_and_types(&action_names_and_types); +} + +py::list +graph_get_action_names_and_types(Node & node) +{ + rcl_names_and_types_t action_names_and_types = rcl_get_zero_initialized_names_and_types(); + rcl_allocator_t allocator = rcl_get_default_allocator(); + rcl_ret_t ret = rcl_action_get_names_and_types( + node.rcl_ptr(), &allocator, &action_names_and_types); + if (RCL_RET_OK != ret) { + throw RCLError("failed to get action names and types"); + } + RCPPUTILS_SCOPE_EXIT( + { + ret = rcl_names_and_types_fini(&action_names_and_types); + if (RCL_RET_OK != ret) { + RCUTILS_SAFE_FWRITE_TO_STDERR( + "[rclpy|" RCUTILS_STRINGIFY(__FILE__) ":" RCUTILS_STRINGIFY(__LINE__) "]: " + "failed to fini action names and types during error handling: "); + RCUTILS_SAFE_FWRITE_TO_STDERR(rcl_get_error_string().str); + RCUTILS_SAFE_FWRITE_TO_STDERR("\n"); + rcl_reset_error(); + } + }); + + return convert_to_py_names_and_types(&action_names_and_types); +} + } // namespace rclpy diff --git a/rclpy/src/rclpy/graph.hpp b/rclpy/src/rclpy/graph.hpp index 2cab27355..0c3591904 100644 --- a/rclpy/src/rclpy/graph.hpp +++ b/rclpy/src/rclpy/graph.hpp @@ -200,6 +200,53 @@ py::list graph_get_servers_info_by_service( Node & node, const char * service_name, bool no_mangle); +/// Get action client names and types by node. +/** + * Raises NodeNameNonExistentError if the remote node was not found + * Raises RCLError if there is an rcl error + * + * \param[in] node Node to get action client names and types + * \param[in] node_name Name of the remote node to query. + * \param[in] node_namespace Namespace of the remote node to query. + * \return List of tuples, where the first element of each tuple is the action + * name (string) and the second element is a list of action types (list of + * strings). + * \see rcl_action_get_client_names_and_types_by_node + */ +py::list +graph_get_action_client_names_and_types_by_node( + Node & node, std::string node_name, std::string node_namespace); + +/// Get action server names and types by node. +/** + * Raises NodeNameNonExistentError if the remote node was not found + * Raises RCLError if there is an rcl error + * + * \param[in] node Node to get action server names and types + * \param[in] node_name Name of the remote node to query. + * \param[in] node_namespace Namespace of the remote node to query. + * \return List of tuples, where the first element of each tuple is the action + * name (string) and the second element is a list of action types (list of + * strings). + * \see rcl_action_get_server_names_and_types_by_node + */ +py::list +graph_get_action_server_names_and_types_by_node( + Node & node, std::string node_name, std::string node_namespace); + +/// Get all action names and types in the ROS graph. +/** + * Raises RCLError if there is an rcl error + * + * \param[in] node Node to get action names and types + * \return List of tuples, where the first element of each tuple is the action + * name (string) and the second element is a list of action types (list of + * strings). + * \see rcl_action_get_names_and_types + */ +py::list +graph_get_action_names_and_types(Node & node); + } // namespace rclpy #endif // RCLPY__GRAPH_HPP_ diff --git a/rclpy/src/rclpy/node.cpp b/rclpy/src/rclpy/node.cpp index cfb30e4b0..30093ec5e 100644 --- a/rclpy/src/rclpy/node.cpp +++ b/rclpy/src/rclpy/node.cpp @@ -535,6 +535,7 @@ py::list Node::get_action_client_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace) { + // Deprecated: Use _rclpy.rclpy_get_action_client_names_and_types_by_node function instead rcl_names_and_types_t names_and_types = rcl_get_zero_initialized_names_and_types(); rcl_allocator_t allocator = rcl_get_default_allocator(); rcl_ret_t ret = rcl_action_get_client_names_and_types_by_node( @@ -554,6 +555,7 @@ py::list Node::get_action_server_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace) { + // Deprecated: Use _rclpy.rclpy_get_action_server_names_and_types_by_node function instead rcl_names_and_types_t names_and_types = rcl_get_zero_initialized_names_and_types(); rcl_allocator_t allocator = rcl_get_default_allocator(); rcl_ret_t ret = rcl_action_get_server_names_and_types_by_node( @@ -572,6 +574,7 @@ Node::get_action_server_names_and_types_by_node( py::list Node::get_action_names_and_types() { + // Deprecated: Use _rclpy.rclpy_get_action_names_and_types function instead rcl_names_and_types_t names_and_types = rcl_get_zero_initialized_names_and_types(); rcl_allocator_t allocator = rcl_get_default_allocator(); rcl_ret_t ret = rcl_action_get_names_and_types(rcl_node_.get(), &allocator, &names_and_types); diff --git a/rclpy/test/test_action_graph.py b/rclpy/test/test_action_graph.py index 7e28bbea6..1213c6b30 100644 --- a/rclpy/test/test_action_graph.py +++ b/rclpy/test/test_action_graph.py @@ -19,10 +19,14 @@ from typing import Optional from typing import TYPE_CHECKING import unittest +import warnings import rclpy from rclpy.action import ActionClient from rclpy.action import ActionServer +# NOTE: The following functions are deprecated in favor of node methods. +# These tests use the deprecated API to ensure backward compatibility. +# Deprecation warnings are expected. from rclpy.action import get_action_client_names_and_types_by_node from rclpy.action import get_action_names_and_types from rclpy.action import get_action_server_names_and_types_by_node @@ -107,7 +111,10 @@ def get_names_and_types( start = time.monotonic() end = start while (end - start) < timeout: - nat = get_names_and_types_func(*args) + # Suppress deprecation warnings for deprecated API tests + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + nat = get_names_and_types_func(*args) if len(nat) == expected_num_names: return nat end = time.monotonic() diff --git a/rclpy/test/test_node.py b/rclpy/test/test_node.py index ee9d1b01f..a29db07ba 100644 --- a/rclpy/test/test_node.py +++ b/rclpy/test/test_node.py @@ -226,6 +226,18 @@ def test_client_names_and_types_by_node(self) -> None: # test that it doesn't raise self.node.get_client_names_and_types_by_node(TEST_NODE, TEST_NAMESPACE) + def test_action_client_names_and_types_by_node(self) -> None: + # test that it doesn't raise + self.node.get_action_client_names_and_types_by_node(TEST_NODE, TEST_NAMESPACE) + + def test_action_server_names_and_types_by_node(self) -> None: + # test that it doesn't raise + self.node.get_action_server_names_and_types_by_node(TEST_NODE, TEST_NAMESPACE) + + def test_action_names_and_types(self) -> None: + # test that it doesn't raise + self.node.get_action_names_and_types() + def test_topic_names_and_types(self) -> None: # test that it doesn't raise self.node.get_topic_names_and_types(no_demangle=True)