From 7055b166c05ce206cbc45eff0458602738247af0 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sat, 1 Aug 2026 14:30:36 -0400 Subject: [PATCH 1/4] Migrate from pybind11 to nanobind Signed-off-by: Sebastian Castro --- rclpy/CMakeLists.txt | 26 +- rclpy/package.xml | 2 +- rclpy/rclpy/duration.py | 2 +- rclpy/rclpy/experimental/events_executor.py | 15 +- ...rclpy_pybind11.pyi => _rclpy_nanobind.pyi} | 2 +- rclpy/rclpy/impl/implementation_singleton.py | 2 +- rclpy/rclpy/impl/implementation_singleton.pyi | 4 +- rclpy/rclpy/lifecycle/__init__.py | 2 +- rclpy/rclpy/time.py | 2 +- rclpy/src/rclpy/_rclpy_logging.cpp | 12 +- ...rclpy_pybind11.cpp => _rclpy_nanobind.cpp} | 54 +-- rclpy/src/rclpy/action_client.cpp | 104 ++--- rclpy/src/rclpy/action_client.hpp | 49 +-- rclpy/src/rclpy/action_goal_handle.cpp | 17 +- rclpy/src/rclpy/action_goal_handle.hpp | 13 +- rclpy/src/rclpy/action_server.cpp | 101 ++--- rclpy/src/rclpy/action_server.hpp | 47 +- rclpy/src/rclpy/client.cpp | 53 +-- rclpy/src/rclpy/client.hpp | 23 +- rclpy/src/rclpy/clock.cpp | 32 +- rclpy/src/rclpy/clock.hpp | 13 +- rclpy/src/rclpy/clock_event.cpp | 15 +- rclpy/src/rclpy/clock_event.hpp | 9 +- rclpy/src/rclpy/context.cpp | 16 +- rclpy/src/rclpy/context.hpp | 11 +- rclpy/src/rclpy/destroyable.cpp | 13 +- rclpy/src/rclpy/destroyable.hpp | 12 +- rclpy/src/rclpy/duration.cpp | 10 +- rclpy/src/rclpy/duration.hpp | 10 +- rclpy/src/rclpy/event_handle.cpp | 119 +++--- rclpy/src/rclpy/event_handle.hpp | 13 +- .../rclpy/events_executor/events_executor.cpp | 401 +++++++++--------- .../rclpy/events_executor/events_executor.hpp | 113 ++--- .../events_executor/python_eq_handler.hpp | 13 +- .../rclpy/events_executor/python_hasher.hpp | 14 +- .../src/rclpy/events_executor/rcl_support.cpp | 10 +- .../src/rclpy/events_executor/scoped_with.hpp | 12 +- .../rclpy/events_executor/timers_manager.cpp | 29 +- .../rclpy/events_executor/timers_manager.hpp | 16 +- rclpy/src/rclpy/graph.cpp | 39 +- rclpy/src/rclpy/graph.hpp | 35 +- rclpy/src/rclpy/guard_condition.cpp | 11 +- rclpy/src/rclpy/guard_condition.hpp | 9 +- rclpy/src/rclpy/lifecycle.cpp | 41 +- rclpy/src/rclpy/lifecycle.hpp | 8 +- rclpy/src/rclpy/logging.cpp | 2 +- rclpy/src/rclpy/logging.hpp | 4 +- rclpy/src/rclpy/logging_api.hpp | 8 +- rclpy/src/rclpy/names.cpp | 33 +- rclpy/src/rclpy/names.hpp | 13 +- rclpy/src/rclpy/node.cpp | 126 +++--- rclpy/src/rclpy/node.hpp | 27 +- rclpy/src/rclpy/publisher.cpp | 30 +- rclpy/src/rclpy/publisher.hpp | 18 +- rclpy/src/rclpy/python_allocator.hpp | 2 +- rclpy/src/rclpy/qos.cpp | 9 +- rclpy/src/rclpy/qos.hpp | 10 +- rclpy/src/rclpy/serialization.cpp | 26 +- rclpy/src/rclpy/serialization.hpp | 12 +- rclpy/src/rclpy/service.cpp | 59 +-- rclpy/src/rclpy/service.hpp | 25 +- rclpy/src/rclpy/service_info.cpp | 16 +- rclpy/src/rclpy/service_info.hpp | 10 +- rclpy/src/rclpy/service_introspection.cpp | 6 +- rclpy/src/rclpy/service_introspection.hpp | 6 +- rclpy/src/rclpy/signal_handler.cpp | 20 +- rclpy/src/rclpy/signal_handler.hpp | 10 +- rclpy/src/rclpy/subscription.cpp | 94 ++-- rclpy/src/rclpy/subscription.hpp | 24 +- rclpy/src/rclpy/time_point.cpp | 12 +- rclpy/src/rclpy/time_point.hpp | 10 +- rclpy/src/rclpy/timer.cpp | 24 +- rclpy/src/rclpy/timer.hpp | 15 +- rclpy/src/rclpy/type_description_service.cpp | 18 +- rclpy/src/rclpy/type_description_service.hpp | 13 +- rclpy/src/rclpy/utils.cpp | 198 ++++----- rclpy/src/rclpy/utils.hpp | 56 ++- rclpy/src/rclpy/wait_set.cpp | 20 +- rclpy/src/rclpy/wait_set.hpp | 12 +- rclpy/test/test_lifecycle.py | 2 +- rclpy/test/test_python_allocator.cpp | 26 +- 81 files changed, 1356 insertions(+), 1194 deletions(-) rename rclpy/rclpy/impl/{_rclpy_pybind11.pyi => _rclpy_nanobind.pyi} (99%) rename rclpy/src/rclpy/{_rclpy_pybind11.cpp => _rclpy_nanobind.cpp} (84%) diff --git a/rclpy/CMakeLists.txt b/rclpy/CMakeLists.txt index af1fc7f8f..d2df75f30 100644 --- a/rclpy/CMakeLists.txt +++ b/rclpy/CMakeLists.txt @@ -38,10 +38,16 @@ find_package(rosidl_runtime_c REQUIRED) cmake_policy(SET CMP0094 NEW) set(Python3_FIND_UNVERSIONED_NAMES FIRST) -# Find python before pybind11 +# Find python before nanobind find_package(Python3 REQUIRED COMPONENTS Interpreter Development) -find_package(pybind11 REQUIRED) +# nanobind's CMake config requires the new-style FindPython module with the +# Python::Module target defined. Reuse the interpreter found above so both +# find_package calls agree on the same Python installation. +set(Python_EXECUTABLE "${Python3_EXECUTABLE}") +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + +find_package(nanobind REQUIRED) # enables using the Python extensions from the build space for testing file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test_rclpy/__init__.py" "") @@ -69,10 +75,10 @@ function(configure_build_install_location _library_name) ) endfunction() -# Split from main extension and converted to pybind11 -pybind11_add_module(_rclpy_pybind11 +# Split from main extension and converted to nanobind +nanobind_add_module(_rclpy_nanobind src/rclpy/_rclpy_logging.cpp - src/rclpy/_rclpy_pybind11.cpp + src/rclpy/_rclpy_nanobind.cpp src/rclpy/action_client.cpp src/rclpy/action_goal_handle.cpp src/rclpy/action_server.cpp @@ -111,13 +117,13 @@ pybind11_add_module(_rclpy_pybind11 ) if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT APPLE) - target_link_libraries(_rclpy_pybind11 PRIVATE atomic) + target_link_libraries(_rclpy_nanobind PRIVATE atomic) endif() -target_include_directories(_rclpy_pybind11 PRIVATE +target_include_directories(_rclpy_nanobind PRIVATE src/rclpy/ ) -target_link_libraries(_rclpy_pybind11 PRIVATE +target_link_libraries(_rclpy_nanobind PRIVATE ament_cmake_ros_core::ament_ros_cxx_standard lifecycle_msgs::lifecycle_msgs__rosidl_generator_c lifecycle_msgs::lifecycle_msgs__rosidl_typesupport_c @@ -130,7 +136,7 @@ target_link_libraries(_rclpy_pybind11 PRIVATE rcutils::rcutils rosidl_runtime_c::rosidl_runtime_c ) -configure_build_install_location(_rclpy_pybind11) +configure_build_install_location(_rclpy_nanobind) if(NOT WIN32) ament_environment_hooks( @@ -175,7 +181,7 @@ if(BUILD_TESTING) target_include_directories(test_python_allocator PRIVATE src/rclpy) target_link_libraries(test_python_allocator ament_cmake_ros_core::ament_ros_cxx_standard - pybind11::embed) + Python3::Python) if(NOT _typesupport_impls STREQUAL "") # Run each test in its own pytest invocation to isolate any global state in rclpy diff --git a/rclpy/package.xml b/rclpy/package.xml index 69f609908..9d2fba8f0 100644 --- a/rclpy/package.xml +++ b/rclpy/package.xml @@ -25,7 +25,7 @@ rcl_lifecycle rcl_logging_interface rcl_yaml_param_parser - pybind11-dev + nanobind-dev python3-dev rcpputils rcutils diff --git a/rclpy/rclpy/duration.py b/rclpy/rclpy/duration.py index 73edc78f5..711586d1f 100644 --- a/rclpy/rclpy/duration.py +++ b/rclpy/rclpy/duration.py @@ -32,7 +32,7 @@ def __init__(self, *, seconds: Union[int, float] = 0, nanoseconds: Union[int, fl total_nanoseconds = int(seconds * S_TO_NS) total_nanoseconds += int(nanoseconds) if total_nanoseconds >= 2**63 or total_nanoseconds < -2**63: - # pybind11 would raise TypeError, but we want OverflowError + # nanobind would raise TypeError, but we want OverflowError raise OverflowError( 'Total nanoseconds value is too large to store in C duration.') self._duration_handle = _rclpy.rcl_duration_t(total_nanoseconds) diff --git a/rclpy/rclpy/experimental/events_executor.py b/rclpy/rclpy/experimental/events_executor.py index c6807bf65..3bc68c009 100644 --- a/rclpy/rclpy/experimental/events_executor.py +++ b/rclpy/rclpy/experimental/events_executor.py @@ -29,15 +29,6 @@ def EventsExecutor(*, context: typing.Optional[rclpy.Context] = None) -> rclpy.e # Python backtrace dumped with the crash. faulthandler.enable() - ex = typing.cast(rclpy.executors.Executor, _rclpy.EventsExecutor(context)) - - # rclpy.Executor does this too. Note, the context itself is smart enough to check - # for bound methods, and check whether the instances they're bound to still exist at - # callback time, so we don't have to worry about tearing down this stale callback at - # destruction time. - # TODO(bmartin427) This should really be done inside of the EventsExecutor - # implementation itself, but I'm unable to figure out a pybind11 incantation that - # allows me to pass this bound method call from C++. - context.on_shutdown(ex.wake) - - return ex + # Note the EventsExecutor implementation takes care of registering a wake-on-shutdown + # callback with the context. + return typing.cast(rclpy.executors.Executor, _rclpy.EventsExecutor(context)) diff --git a/rclpy/rclpy/impl/_rclpy_pybind11.pyi b/rclpy/rclpy/impl/_rclpy_nanobind.pyi similarity index 99% rename from rclpy/rclpy/impl/_rclpy_pybind11.pyi rename to rclpy/rclpy/impl/_rclpy_nanobind.pyi index 4bd8e913d..f287815b1 100644 --- a/rclpy/rclpy/impl/_rclpy_pybind11.pyi +++ b/rclpy/rclpy/impl/_rclpy_nanobind.pyi @@ -52,7 +52,7 @@ from type_description_interfaces.srv import GetTypeDescription T = TypeVar('T') -# All things are defined in same order as defined in _rclpy_pybind11.cpp +# All things are defined in same order as defined in _rclpy_nanobind.cpp class Destroyable: diff --git a/rclpy/rclpy/impl/implementation_singleton.py b/rclpy/rclpy/impl/implementation_singleton.py index 3e312280b..0f7e03b2f 100644 --- a/rclpy/rclpy/impl/implementation_singleton.py +++ b/rclpy/rclpy/impl/implementation_singleton.py @@ -30,4 +30,4 @@ package = 'rclpy' -rclpy_implementation = import_c_library('._rclpy_pybind11', package) +rclpy_implementation = import_c_library('._rclpy_nanobind', package) diff --git a/rclpy/rclpy/impl/implementation_singleton.pyi b/rclpy/rclpy/impl/implementation_singleton.pyi index a1e16bdf9..182c3495d 100644 --- a/rclpy/rclpy/impl/implementation_singleton.pyi +++ b/rclpy/rclpy/impl/implementation_singleton.pyi @@ -13,6 +13,6 @@ # limitations under the License. -from rclpy.impl import _rclpy_pybind11 +from rclpy.impl import _rclpy_nanobind -rclpy_implementation = _rclpy_pybind11 +rclpy_implementation = _rclpy_nanobind diff --git a/rclpy/rclpy/lifecycle/__init__.py b/rclpy/rclpy/lifecycle/__init__.py index 64f168873..61201437b 100644 --- a/rclpy/rclpy/lifecycle/__init__.py +++ b/rclpy/rclpy/lifecycle/__init__.py @@ -30,7 +30,7 @@ State = LifecycleState Publisher = LifecyclePublisher -# enum defined in pybind11 plugin +# enum defined in nanobind plugin TransitionCallbackReturn = _rclpy.TransitionCallbackReturnType diff --git a/rclpy/rclpy/time.py b/rclpy/rclpy/time.py index 2ede55d5b..3531e9cda 100644 --- a/rclpy/rclpy/time.py +++ b/rclpy/rclpy/time.py @@ -51,7 +51,7 @@ def __init__( total_nanoseconds = int(seconds * S_TO_NS) total_nanoseconds += int(nanoseconds) if total_nanoseconds >= 2**63: - # pybind11 would raise TypeError, but we want OverflowError + # nanobind would raise TypeError, but we want OverflowError raise OverflowError( 'Total nanoseconds value is too large to store in C time point.') self._time_handle = _rclpy.rcl_time_point_t(total_nanoseconds, clock_type) diff --git a/rclpy/src/rclpy/_rclpy_logging.cpp b/rclpy/src/rclpy/_rclpy_logging.cpp index 6d0eb283c..fb7dd00c1 100644 --- a/rclpy/src/rclpy/_rclpy_logging.cpp +++ b/rclpy/src/rclpy/_rclpy_logging.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include -namespace py = pybind11; +namespace nb = nanobind; +using nb::literals::operator""_a; #include #include @@ -237,9 +239,9 @@ rclpy_logging_rosout_remove_sublogger(const char * logger_name, const char * sub namespace rclpy { void -define_logging_api(py::module m) +define_logging_api(nb::module_ m) { - py::enum_(m, "RCUTILS_LOG_SEVERITY") + nb::enum_(m, "RCUTILS_LOG_SEVERITY", nb::is_arithmetic()) .value("RCUTILS_LOG_SEVERITY_UNSET", RCUTILS_LOG_SEVERITY_UNSET) .value("RCUTILS_LOG_SEVERITY_DEBUG", RCUTILS_LOG_SEVERITY_DEBUG) .value("RCUTILS_LOG_SEVERITY_INFO", RCUTILS_LOG_SEVERITY_INFO) @@ -252,7 +254,7 @@ define_logging_api(py::module m) m.def("rclpy_logging_shutdown", &rclpy_logging_shutdown); m.def( "rclpy_logging_set_logger_level", &rclpy_logging_set_logger_level, - py::arg("name"), py::arg("level"), py::arg("detailed_error") = false); + "name"_a, "level"_a, "detailed_error"_a = false); m.def("rclpy_logging_get_logger_effective_level", &rclpy_logging_get_logger_effective_level); m.def("rclpy_logging_logger_is_enabled_for", &rclpy_logging_logger_is_enabled_for); m.def("rclpy_logging_rcutils_log", &rclpy_logging_rcutils_log); diff --git a/rclpy/src/rclpy/_rclpy_pybind11.cpp b/rclpy/src/rclpy/_rclpy_nanobind.cpp similarity index 84% rename from rclpy/src/rclpy/_rclpy_pybind11.cpp rename to rclpy/src/rclpy/_rclpy_nanobind.cpp index ce3dea8df..1bb2321a1 100644 --- a/rclpy/src/rclpy/_rclpy_pybind11.cpp +++ b/rclpy/src/rclpy/_rclpy_nanobind.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include @@ -55,34 +55,34 @@ #include "utils.hpp" #include "wait_set.hpp" -namespace py = pybind11; +namespace nb = nanobind; -PYBIND11_MODULE(_rclpy_pybind11, m) { +NB_MODULE(_rclpy_nanobind, m) { m.doc() = "ROS 2 Python client library."; rclpy::define_destroyable(m); - py::enum_(m, "ClockType") + nb::enum_(m, "ClockType", nb::is_arithmetic()) .value("UNINITIALIZED", RCL_CLOCK_UNINITIALIZED) .value("ROS_TIME", RCL_ROS_TIME) .value("SYSTEM_TIME", RCL_SYSTEM_TIME) .value("STEADY_TIME", RCL_STEADY_TIME); - py::enum_(m, "GoalEvent") + nb::enum_(m, "GoalEvent", nb::is_arithmetic()) .value("EXECUTE", GOAL_EVENT_EXECUTE) .value("CANCEL_GOAL", GOAL_EVENT_CANCEL_GOAL) .value("SUCCEED", GOAL_EVENT_SUCCEED) .value("ABORT", GOAL_EVENT_ABORT) .value("CANCELED", GOAL_EVENT_CANCELED); - m.attr("RCL_DEFAULT_DOMAIN_ID") = py::int_(RCL_DEFAULT_DOMAIN_ID); - m.attr("RMW_DURATION_INFINITE") = py::int_(rmw_time_total_nsec(RMW_DURATION_INFINITE)); - m.attr("RMW_QOS_DEADLINE_BEST_AVAILABLE") = py::int_( + m.attr("RCL_DEFAULT_DOMAIN_ID") = nb::int_(RCL_DEFAULT_DOMAIN_ID); + m.attr("RMW_DURATION_INFINITE") = nb::int_(rmw_time_total_nsec(RMW_DURATION_INFINITE)); + m.attr("RMW_QOS_DEADLINE_BEST_AVAILABLE") = nb::int_( rmw_time_total_nsec(RMW_QOS_DEADLINE_BEST_AVAILABLE)); - m.attr("RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE") = py::int_( + m.attr("RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE") = nb::int_( rmw_time_total_nsec(RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE)); - py::enum_(m, "ClockChange") + nb::enum_(m, "ClockChange", nb::is_arithmetic()) .value( "ROS_TIME_NO_CHANGE", RCL_ROS_TIME_NO_CHANGE, "ROS time is active and will continue to be active") @@ -96,33 +96,33 @@ PYBIND11_MODULE(_rclpy_pybind11, m) { "SYSTEM_TIME_NO_CHANGE", RCL_SYSTEM_TIME_NO_CHANGE, "ROS time is inactive and the clock will keep reporting system time"); - py::enum_(m, "QoSCompatibility") + nb::enum_(m, "QoSCompatibility", nb::is_arithmetic()) .value("OK", RMW_QOS_COMPATIBILITY_OK) .value("WARNING", RMW_QOS_COMPATIBILITY_WARNING) .value("ERROR", RMW_QOS_COMPATIBILITY_ERROR); - py::class_( + nb::class_( m, "QoSCheckCompatibleResult", "Result type for checking QoS compatibility with result") - .def(py::init<>()) - .def_readonly("compatibility", &rclpy::QoSCheckCompatibleResult::compatibility) - .def_readonly("reason", &rclpy::QoSCheckCompatibleResult::reason); - - py::register_exception(m, "RCUtilsError", PyExc_RuntimeError); - py::register_exception(m, "RMWError", PyExc_RuntimeError); - auto rclerror = py::register_exception(m, "RCLError", PyExc_RuntimeError); - py::register_exception( + .def(nb::init<>()) + .def_ro("compatibility", &rclpy::QoSCheckCompatibleResult::compatibility) + .def_ro("reason", &rclpy::QoSCheckCompatibleResult::reason); + + nb::exception(m, "RCUtilsError", PyExc_RuntimeError); + nb::exception(m, "RMWError", PyExc_RuntimeError); + auto rclerror = nb::exception(m, "RCLError", PyExc_RuntimeError); + nb::exception( m, "RCLInvalidROSArgsError", rclerror.ptr()); - py::register_exception(m, "UnknownROSArgsError", PyExc_RuntimeError); - py::register_exception( + nb::exception(m, "UnknownROSArgsError", PyExc_RuntimeError); + nb::exception( m, "NodeNameNonExistentError", rclerror.ptr()); - py::register_exception( + nb::exception( m, "UnsupportedEventTypeError", rclerror.ptr()); - py::register_exception( + nb::exception( m, "TimerCancelledError", rclerror.ptr()); - py::register_exception( + nb::exception( m, "NotImplementedError", PyExc_NotImplementedError); - py::register_exception( + nb::exception( m, "InvalidHandle", PyExc_RuntimeError); rclpy::define_service_introspection(m); @@ -261,7 +261,7 @@ PYBIND11_MODULE(_rclpy_pybind11, m) { "Assert the liveliness of an entity."); m.def( - "rclpy_remove_ros_args", &rclpy::remove_ros_args, + "rclpy_remove_ros_args", &rclpy::remove_ros_args, nb::arg().none(), "Remove ROS-specific arguments from argument vector."); rclpy::define_rmw_qos_profile(m); diff --git a/rclpy/src/rclpy/action_client.cpp b/rclpy/src/rclpy/action_client.cpp index 056602755..862bd4d47 100644 --- a/rclpy/src/rclpy/action_client.cpp +++ b/rclpy/src/rclpy/action_client.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -29,6 +31,8 @@ #include "node.hpp" #include "utils.hpp" +using nb::literals::operator""_a; + namespace rclpy { @@ -41,7 +45,7 @@ ActionClient::destroy() ActionClient::ActionClient( Node & node, - py::object pyaction_type, + nb::object pyaction_type, const char * action_name, const rmw_qos_profile_t & goal_service_qos, const rmw_qos_profile_t & result_service_qos, @@ -55,7 +59,7 @@ ActionClient::ActionClient( action_type_support_ = static_cast(common_get_type_support(pyaction_type)); if (!action_type_support_) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_action_client_options_t action_client_ops = rcl_action_client_get_default_options(); @@ -92,13 +96,13 @@ ActionClient::ActionClient( error_text += "' : "; error_text += rcl_get_error_string().str; rcl_reset_error(); - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } if (RCL_RET_OK != ret) { std::string error_text{"Failed to create action client: "}; error_text += rcl_get_error_string().str; rcl_reset_error(); - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } } @@ -111,14 +115,14 @@ ActionClient::ActionClient( int64_t sequence = header.sequence_number; \ /* Create the tuple to return */ \ if (RCL_RET_ACTION_CLIENT_TAKE_FAILED == ret || RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) { \ - return py::make_tuple(py::none(), py::none()); \ + return nb::make_tuple(nb::none(), nb::none()); \ } else if (RCL_RET_OK != ret) { \ throw rclpy::RCLError("Failed to take " #Type); \ } \ - return py::make_tuple(sequence, convert_to_py(taken_msg.get(), pymsg_type)); \ + return nb::make_tuple(sequence, convert_to_py(taken_msg.get(), pymsg_type)); \ -py::tuple -ActionClient::take_goal_response(py::object pymsg_type) +nb::tuple +ActionClient::take_goal_response(nb::object pymsg_type) { TAKE_SERVICE_RESPONSE(goal) } @@ -134,13 +138,13 @@ ActionClient::take_goal_response(py::object pymsg_type) return sequence_number; int64_t -ActionClient::send_result_request(py::object pyrequest) +ActionClient::send_result_request(nb::object pyrequest) { SEND_SERVICE_REQUEST(result); } -py::tuple -ActionClient::take_cancel_response(py::object pymsg_type) +nb::tuple +ActionClient::take_cancel_response(nb::object pymsg_type) { TAKE_SERVICE_RESPONSE(cancel) } @@ -151,43 +155,43 @@ ActionClient::take_cancel_response(py::object pymsg_type) if (RCL_RET_OK != ret) { \ if (RCL_RET_ACTION_CLIENT_TAKE_FAILED == ret) { \ /* if take failed, just do nothing */ \ - return py::none(); \ + return nb::none(); \ } \ throw rclpy::RCLError("Failed to take " #Type " with an action client"); \ } \ return convert_to_py(taken_msg.get(), pymsg_type); -py::object -ActionClient::take_feedback(py::object pymsg_type) +nb::object +ActionClient::take_feedback(nb::object pymsg_type) { TAKE_MESSAGE(feedback) } -py::object -ActionClient::take_status(py::object pymsg_type) +nb::object +ActionClient::take_status(nb::object pymsg_type) { TAKE_MESSAGE(status) } int64_t -ActionClient::send_cancel_request(py::object pyrequest) +ActionClient::send_cancel_request(nb::object pyrequest) { SEND_SERVICE_REQUEST(cancel) } int64_t -ActionClient::send_goal_request(py::object pyrequest) +ActionClient::send_goal_request(nb::object pyrequest) { SEND_SERVICE_REQUEST(goal) } -py::tuple -ActionClient::take_result_response(py::object pymsg_type) +nb::tuple +ActionClient::take_result_response(nb::object pymsg_type) { TAKE_SERVICE_RESPONSE(result); } -py::tuple +nb::tuple ActionClient::get_num_entities() { size_t num_subscriptions = 0u; @@ -209,7 +213,7 @@ ActionClient::get_num_entities() throw rclpy::RCLError(error_text); } - return py::make_tuple( + return nb::make_tuple( num_subscriptions, num_guard_conditions, num_timers, num_clients, num_services); } @@ -237,7 +241,7 @@ ActionClient::add_to_waitset(WaitSet & wait_set) } } -py::tuple +nb::tuple ActionClient::is_ready(WaitSet & wait_set) { bool is_feedback_ready = false; @@ -257,24 +261,23 @@ ActionClient::is_ready(WaitSet & wait_set) throw rclpy::RCLError("Failed to get number of ready entities for action client"); } - py::tuple result_tuple(5); - result_tuple[0] = py::bool_(is_feedback_ready); - result_tuple[1] = py::bool_(is_status_ready); - result_tuple[2] = py::bool_(is_goal_response_ready); - result_tuple[3] = py::bool_(is_cancel_response_ready); - result_tuple[4] = py::bool_(is_result_response_ready); - return result_tuple; + return nb::make_tuple( + is_feedback_ready, + is_status_ready, + is_goal_response_ready, + is_cancel_response_ready, + is_result_response_ready); } void ActionClient::configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); pub_opts.qos = pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - pyqos_service_event_pub.cast(); + nb::cast(pyqos_service_event_pub); rcl_ret_t ret = rcl_action_client_configure_action_introspection( rcl_action_client_.get(), node_.rcl_ptr(), clock.rcl_ptr(), @@ -286,7 +289,7 @@ ActionClient::configure_introspection( } bool -ActionClient::configure_feedback_subscription_filter_add_goal_id(py::bytes goal_id) +ActionClient::configure_feedback_subscription_filter_add_goal_id(nb::bytes goal_id) { std::lock_guard lock(configure_feedback_sub_content_filter_mutex_); @@ -294,7 +297,7 @@ ActionClient::configure_feedback_subscription_filter_add_goal_id(py::bytes goal_ return false; } - std::string str_goal_id = static_cast(goal_id); + std::string str_goal_id(goal_id.c_str(), goal_id.size()); const uint8_t * goal_id_array = reinterpret_cast(str_goal_id.data()); rcl_ret_t ret = rcl_action_client_configure_feedback_subscription_filter_add_goal_id( rcl_action_client_.get(), goal_id_array, str_goal_id.size()); @@ -312,7 +315,7 @@ ActionClient::configure_feedback_subscription_filter_add_goal_id(py::bytes goal_ } bool -ActionClient::configure_feedback_subscription_filter_remove_goal_id(py::bytes goal_id) +ActionClient::configure_feedback_subscription_filter_remove_goal_id(nb::bytes goal_id) { std::lock_guard lock(configure_feedback_sub_content_filter_mutex_); @@ -320,7 +323,7 @@ ActionClient::configure_feedback_subscription_filter_remove_goal_id(py::bytes go return false; } - std::string str_goal_id = static_cast(goal_id); + std::string str_goal_id(goal_id.c_str(), goal_id.size()); const uint8_t * goal_id_array = reinterpret_cast(str_goal_id.data()); rcl_ret_t ret = rcl_action_client_configure_feedback_subscription_filter_remove_goal_id( rcl_action_client_.get(), goal_id_array, str_goal_id.size()); @@ -338,23 +341,23 @@ ActionClient::configure_feedback_subscription_filter_remove_goal_id(py::bytes go } void -define_action_client(py::object module) +define_action_client(nb::object module) { - py::class_>(module, "ActionClient") + nb::class_(module, "ActionClient") .def( - py::init(), - py::arg("node"), - py::arg("action_type"), - py::arg("action_name"), - py::arg("goal_service_qos_profile"), - py::arg("result_service_qos_profile"), - py::arg("cancel_service_qos_profile"), - py::arg("feedback_sub_qos_profile"), - py::arg("status_sub_qos_profile"), - py::arg("enable_feedback_msg_optimization") = false) - .def_property_readonly( + "node"_a, + "action_type"_a, + "action_name"_a, + "goal_service_qos_profile"_a, + "result_service_qos_profile"_a, + "cancel_service_qos_profile"_a, + "feedback_sub_qos_profile"_a, + "status_sub_qos_profile"_a, + "enable_feedback_msg_optimization"_a = false) + .def_prop_ro( "pointer", [](const ActionClient & action_client) { return reinterpret_cast(action_client.rcl_ptr()); }, @@ -397,6 +400,7 @@ define_action_client(py::object module) "Take an action status response.") .def( "configure_introspection", &ActionClient::configure_introspection, + nb::arg(), nb::arg().none(), nb::arg(), "Configure whether internal client introspection is enabled") .def( "configure_feedback_subscription_filter_add_goal_id", diff --git a/rclpy/src/rclpy/action_client.hpp b/rclpy/src/rclpy/action_client.hpp index 666f7c3ac..a8c8773b8 100644 --- a/rclpy/src/rclpy/action_client.hpp +++ b/rclpy/src/rclpy/action_client.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__ACTION_CLIENT_HPP_ #define RCLPY__ACTION_CLIENT_HPP_ -#include +#include +#include #include #include @@ -28,7 +29,7 @@ #include "node.hpp" #include "wait_set.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -70,7 +71,7 @@ class ActionClient : public Destroyable, public std::enable_shared_from_this +#include +#include #include #include @@ -26,12 +27,12 @@ #include "exceptions.hpp" #include "utils.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { ActionGoalHandle::ActionGoalHandle( - rclpy::ActionServer & action_server, py::object pygoal_info_msg) + rclpy::ActionServer & action_server, nb::object pygoal_info_msg) : action_server_(action_server) { auto goal_info_msg = convert_from_py(pygoal_info_msg); @@ -39,7 +40,7 @@ ActionGoalHandle::ActionGoalHandle( static_cast(goal_info_msg.get()); if (!goal_info_msg) { - throw py::error_already_set(); + throw nb::python_error(); } auto rcl_handle = rcl_action_accept_new_goal( @@ -96,12 +97,12 @@ ActionGoalHandle::update_goal_state(rcl_action_goal_event_t event) } void -define_action_goal_handle(py::module module) +define_action_goal_handle(nb::module_ module) { - py::class_>( + nb::class_( module, "ActionGoalHandle") - .def(py::init()) - .def_property_readonly( + .def(nb::init()) + .def_prop_ro( "pointer", [](const ActionGoalHandle & handle) { return reinterpret_cast(handle.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/action_goal_handle.hpp b/rclpy/src/rclpy/action_goal_handle.hpp index 7bca00f3c..d8b73fa43 100644 --- a/rclpy/src/rclpy/action_goal_handle.hpp +++ b/rclpy/src/rclpy/action_goal_handle.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__ACTION_GOAL_HANDLE_HPP_ #define RCLPY__ACTION_GOAL_HANDLE_HPP_ -#include +#include +#include #include #include @@ -25,7 +26,7 @@ #include "action_server.hpp" #include "destroyable.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -44,7 +45,7 @@ class ActionGoalHandle : public Destroyable, public std::enable_shared_from_this * \param[in] pyaction_server handle to the action server that is accepting the goal * \param[in] pygoal_info_msg a message containing info about the goal being accepted */ - ActionGoalHandle(rclpy::ActionServer & action_server, py::object pygoal_info_msg); + ActionGoalHandle(rclpy::ActionServer & action_server, nb::object pygoal_info_msg); ~ActionGoalHandle() = default; @@ -77,12 +78,12 @@ class ActionGoalHandle : public Destroyable, public std::enable_shared_from_this std::shared_ptr rcl_action_goal_handle_; }; -/// Define a pybind11 wrapper for an rclpy::ActionGoalHandle +/// Define a nanobind wrapper for an rclpy::ActionGoalHandle /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_action_goal_handle(py::module module); +define_action_goal_handle(nb::module_ module); } // namespace rclpy #endif // RCLPY__ACTION_GOAL_HANDLE_HPP_ diff --git a/rclpy/src/rclpy/action_server.cpp b/rclpy/src/rclpy/action_server.cpp index 098f19f17..9670df4a3 100644 --- a/rclpy/src/rclpy/action_server.cpp +++ b/rclpy/src/rclpy/action_server.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -47,7 +49,7 @@ ActionServer::destroy() ActionServer::ActionServer( Node & node, const rclpy::Clock & rclpy_clock, - py::object pyaction_type, + nb::object pyaction_type, const char * action_name, const rmw_qos_profile_t & goal_service_qos, const rmw_qos_profile_t & result_service_qos, @@ -62,7 +64,7 @@ ActionServer::ActionServer( action_type_support_ = static_cast( common_get_type_support(pyaction_type)); if (!action_type_support_) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_action_server_options_t action_server_ops = rcl_action_server_get_default_options(); @@ -99,9 +101,9 @@ ActionServer::ActionServer( std::string error_text{"Failed to create action server due to invalid topic name '"}; error_text += action_name; error_text += "' : "; - throw py::value_error(append_rcl_error(error_text)); + throw nb::value_error(append_rcl_error(error_text).c_str()); } else if (RCL_RET_OK != ret) { - throw py::value_error(append_rcl_error("Failed to create action server")); + throw nb::value_error(append_rcl_error("Failed to create action server").c_str()); } } @@ -113,20 +115,20 @@ ActionServer::ActionServer( rcl_action_take_ ## Type ## _request(rcl_action_server_.get(), &header, taken_msg.get()); \ /* Create the tuple to return */ \ if (ret == RCL_RET_ACTION_CLIENT_TAKE_FAILED || ret == RCL_RET_ACTION_SERVER_TAKE_FAILED) { \ - return py::make_tuple(py::none(), py::none()); \ + return nb::make_tuple(nb::none(), nb::none()); \ } else if (RCL_RET_OK != ret) { \ throw rclpy::RCLError("Failed to take " #Type); \ } \ - return py::make_tuple(header, convert_to_py(taken_msg.get(), pymsg_type)); \ + return nb::make_tuple(header, convert_to_py(taken_msg.get(), pymsg_type)); \ -py::tuple -ActionServer::take_goal_request(py::object pymsg_type) +nb::tuple +ActionServer::take_goal_request(nb::object pymsg_type) { TAKE_SERVICE_REQUEST(goal) } -py::tuple -ActionServer::take_result_request(py::object pymsg_type) +nb::tuple +ActionServer::take_result_request(nb::object pymsg_type) { TAKE_SERVICE_REQUEST(result) } @@ -149,33 +151,33 @@ ActionServer::take_result_request(py::object pymsg_type) void ActionServer::send_goal_response( - rmw_request_id_t * header, py::object pyresponse) + rmw_request_id_t * header, nb::object pyresponse) { SEND_SERVICE_RESPONSE(goal) } void ActionServer::send_result_response( - rmw_request_id_t * header, py::object pyresponse) + rmw_request_id_t * header, nb::object pyresponse) { SEND_SERVICE_RESPONSE(result) } -py::tuple -ActionServer::take_cancel_request(py::object pymsg_type) +nb::tuple +ActionServer::take_cancel_request(nb::object pymsg_type) { TAKE_SERVICE_REQUEST(cancel) } void ActionServer::send_cancel_response( - rmw_request_id_t * header, py::object pyresponse) + rmw_request_id_t * header, nb::object pyresponse) { SEND_SERVICE_RESPONSE(cancel) } void -ActionServer::publish_feedback(py::object pymsg) +ActionServer::publish_feedback(nb::object pymsg) { auto ros_message = convert_from_py(pymsg); rcl_ret_t ret = rcl_action_publish_feedback(rcl_action_server_.get(), ros_message.get()); @@ -224,14 +226,14 @@ ActionServer::notify_goal_done() } bool -ActionServer::goal_exists(py::object pygoal_info) +ActionServer::goal_exists(nb::object pygoal_info) { auto goal_info = convert_from_py(pygoal_info); rcl_action_goal_info_t * goal_info_type = static_cast(goal_info.get()); return rcl_action_server_goal_exists(rcl_action_server_.get(), goal_info_type); } -py::tuple +nb::tuple ActionServer::get_num_entities() { size_t num_subscriptions = 0u; @@ -252,16 +254,16 @@ ActionServer::get_num_entities() throw rclpy::RCLError("Failed to get number of entities for 'rcl_action_server_t'"); } - py::tuple result_tuple(5); - result_tuple[0] = py::int_(num_subscriptions); - result_tuple[1] = py::int_(num_guard_conditions); - result_tuple[2] = py::int_(num_timers); - result_tuple[3] = py::int_(num_clients); - result_tuple[4] = py::int_(num_services); + nb::tuple result_tuple = nb::make_tuple( + num_subscriptions, + num_guard_conditions, + num_timers, + num_clients, + num_services); return result_tuple; } -py::tuple +nb::tuple ActionServer::is_ready(WaitSet & wait_set) { bool is_goal_request_ready = false; @@ -280,12 +282,11 @@ ActionServer::is_ready(WaitSet & wait_set) throw rclpy::RCLError("Failed to get number of ready entities for action server"); } - py::tuple result_tuple(4); - result_tuple[0] = py::bool_(is_goal_request_ready); - result_tuple[1] = py::bool_(is_cancel_request_ready); - result_tuple[2] = py::bool_(is_result_request_ready); - result_tuple[3] = py::bool_(is_goal_expired); - return result_tuple; + return nb::make_tuple( + is_goal_request_ready, + is_cancel_request_ready, + is_result_request_ready, + is_goal_expired); } void @@ -298,9 +299,9 @@ ActionServer::add_to_waitset(WaitSet & wait_set) } } -py::object +nb::object ActionServer::process_cancel_request( - py::object pycancel_request, py::object pycancel_response_type) + nb::object pycancel_request, nb::object pycancel_response_type) { auto cancel_request = convert_from_py(pycancel_request); rcl_action_cancel_request_t * cancel_request_tmp = static_cast( @@ -319,7 +320,7 @@ ActionServer::process_cancel_request( throw std::runtime_error(error_text); } - py::object return_value = convert_to_py(&cancel_response.msg, pycancel_response_type); + nb::object return_value = convert_to_py(&cancel_response.msg, pycancel_response_type); RCPPUTILS_SCOPE_EXIT( { ret = rcl_action_cancel_response_fini(&cancel_response); @@ -335,7 +336,7 @@ ActionServer::process_cancel_request( return return_value; } -py::tuple +nb::tuple ActionServer::expire_goals(int64_t max_num_goals) { auto expired_goals = @@ -348,30 +349,29 @@ ActionServer::expire_goals(int64_t max_num_goals) } // Get Python GoalInfo type - py::module pyaction_msgs_module = py::module::import("action_msgs.msg"); - py::object pygoal_info_class = pyaction_msgs_module.attr("GoalInfo"); - py::object pygoal_info_type = pygoal_info_class(); + nb::module_ pyaction_msgs_module = nb::module_::import_("action_msgs.msg"); + nb::object pygoal_info_class = pyaction_msgs_module.attr("GoalInfo"); + nb::object pygoal_info_type = pygoal_info_class(); // Create a tuple of GoalInfo instances to return - py::tuple result_tuple(num_expired); - + nb::list expired_list; for (size_t i = 0; i < num_expired; ++i) { - result_tuple[i] = - convert_to_py(&(expired_goals.get()[i]), pygoal_info_type); + expired_list.append( + convert_to_py(&(expired_goals.get()[i]), pygoal_info_type)); } - return result_tuple; + return nb::tuple(expired_list); } void ActionServer::configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); pub_opts.qos = pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - pyqos_service_event_pub.cast(); + nb::cast(pyqos_service_event_pub); rcl_ret_t ret = rcl_action_server_configure_action_introspection( rcl_action_server_.get(), node_.rcl_ptr(), clock.rcl_ptr(), @@ -383,14 +383,14 @@ ActionServer::configure_introspection( } void -define_action_server(py::object module) +define_action_server(nb::object module) { - py::class_>(module, "ActionServer") + nb::class_(module, "ActionServer") .def( - py::init()) - .def_property_readonly( + .def_prop_ro( "pointer", [](const ActionServer & action_server) { return reinterpret_cast(action_server.rcl_ptr()); }, @@ -442,6 +442,7 @@ define_action_server(py::object module) "Add an action entity to a wait set.") .def( "configure_introspection", &ActionServer::configure_introspection, + nb::arg(), nb::arg().none(), nb::arg(), "Configure whether internal service introspection is enabled"); } diff --git a/rclpy/src/rclpy/action_server.hpp b/rclpy/src/rclpy/action_server.hpp index 4aa96c1e0..27be70fd4 100644 --- a/rclpy/src/rclpy/action_server.hpp +++ b/rclpy/src/rclpy/action_server.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__ACTION_SERVER_HPP_ #define RCLPY__ACTION_SERVER_HPP_ -#include +#include +#include #include #include @@ -27,7 +28,7 @@ #include "node.hpp" #include "wait_set.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -59,7 +60,7 @@ class ActionServer : public Destroyable, public std::enable_shared_from_this rcl_action_server_; const rosidl_action_type_support_t * action_type_support_; }; -/// Define a pybind11 wrapper for an rclpy::ActionServer +/// Define a nanobind wrapper for an rclpy::ActionServer /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ -void define_action_server(py::object module); +void define_action_server(nb::object module); } // namespace rclpy #endif // RCLPY__ACTION_SERVER_HPP_ diff --git a/rclpy/src/rclpy/client.cpp b/rclpy/src/rclpy/client.cpp index 5ee4405ee..e6033f379 100644 --- a/rclpy/src/rclpy/client.cpp +++ b/rclpy/src/rclpy/client.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include +#include +#include +#include #include #include @@ -34,6 +36,8 @@ #include "utils.hpp" #include "events_executor/rcl_support.hpp" +using nb::literals::operator""_a; + namespace rclpy { using events_executor::RclEventCallbackTrampoline; @@ -50,18 +54,18 @@ Client::destroy() } Client::Client( - Node & node, py::object pysrv_type, const std::string & service_name, py::object pyqos_profile) + Node & node, nb::object pysrv_type, const std::string & service_name, nb::object pyqos_profile) : node_(node) { srv_type_ = static_cast(common_get_type_support(pysrv_type)); if (nullptr == srv_type_) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_client_options_t client_ops = rcl_client_get_default_options(); if (!pyqos_profile.is_none()) { - client_ops.qos = pyqos_profile.cast(); + client_ops.qos = nb::cast(pyqos_profile); } // Create a client @@ -88,18 +92,18 @@ Client::Client( error_text += "': "; error_text += rcl_get_error_string().str; rcl_reset_error(); - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } throw RCLError("failed to create client"); } } int64_t -Client::send_request(py::object pyrequest) +Client::send_request(nb::object pyrequest) { auto raw_ros_request = convert_from_py(pyrequest); if (!raw_ros_request) { - throw py::error_already_set(); + throw nb::python_error(); } int64_t sequence_number; @@ -123,40 +127,34 @@ Client::service_server_is_available() return is_ready; } -py::tuple -Client::take_response(py::object pyresponse_type) +nb::tuple +Client::take_response(nb::object pyresponse_type) { auto taken_response = create_from_py(pyresponse_type); rmw_service_info_t header; - py::tuple result_tuple(2); rcl_ret_t ret = rcl_take_response_with_info( rcl_client_.get(), &header, taken_response.get()); if (ret == RCL_RET_CLIENT_TAKE_FAILED) { - result_tuple[0] = py::none(); - result_tuple[1] = py::none(); - return result_tuple; + return nb::make_tuple(nb::none(), nb::none()); } if (RCL_RET_OK != ret) { throw RCLError("encountered error when taking client response"); } - result_tuple[0] = header; - result_tuple[1] = convert_to_py(taken_response.get(), pyresponse_type); - - return result_tuple; + return nb::make_tuple(header, convert_to_py(taken_response.get(), pyresponse_type)); } void Client::configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); pub_opts.qos = pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - pyqos_service_event_pub.cast(); + nb::cast(pyqos_service_event_pub); rcl_ret_t ret = rcl_client_configure_service_introspection( rcl_client_.get(), node_.rcl_ptr(), clock.rcl_ptr(), srv_type_, pub_opts, introspection_state); @@ -219,14 +217,16 @@ Client::clear_on_new_response_callback() } void -define_client(py::object module) +define_client(nb::object module) { - py::class_>(module, "Client") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Client") + .def( + nb::init(), + nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def_prop_ro( "service_name", &Client::get_service_name, "Get the name of the service") - .def_property_readonly( + .def_prop_ro( "pointer", [](const Client & client) { return reinterpret_cast(client.rcl_ptr()); }, @@ -242,13 +242,14 @@ define_client(py::object module) "Take a received response from an earlier request") .def( "configure_introspection", &Client::configure_introspection, + nb::arg(), nb::arg().none(), nb::arg(), "Configure whether introspection is enabled") .def( "get_logger_name", &Client::get_logger_name, "Get the name of the logger associated with the node of the client.") .def( "set_on_new_response_callback", &Client::set_on_new_response_callback, - py::arg("callback")) + "callback"_a) .def("clear_on_new_response_callback", &Client::clear_on_new_response_callback); } } // namespace rclpy diff --git a/rclpy/src/rclpy/client.hpp b/rclpy/src/rclpy/client.hpp index f31e0acb7..d83c09ebd 100644 --- a/rclpy/src/rclpy/client.hpp +++ b/rclpy/src/rclpy/client.hpp @@ -15,7 +15,10 @@ #ifndef RCLPY__CLIENT_HPP_ #define RCLPY__CLIENT_HPP_ -#include +#include +#include +#include +#include #include #include @@ -29,7 +32,7 @@ #include "destroyable.hpp" #include "node.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -50,7 +53,7 @@ class Client : public Destroyable, public std::enable_shared_from_this * \param[in] service_name The service name * \param[in] pyqos QoSProfile python object for this client */ - Client(Node & node, py::object pysrv_type, const std::string & service_name, py::object pyqos); + Client(Node & node, nb::object pysrv_type, const std::string & service_name, nb::object pyqos); ~Client() = default; @@ -63,7 +66,7 @@ class Client : public Destroyable, public std::enable_shared_from_this * \return sequence_number Index of the sent request */ int64_t - send_request(py::object pyrequest); + send_request(nb::object pyrequest); /// Check if a service server is available /** @@ -81,8 +84,8 @@ class Client : public Destroyable, public std::enable_shared_from_this * \param[in] pyresponse_type Instance of the message type to take * \return 2-tuple sequence number and received response, or None if there is no response */ - py::tuple - take_response(py::object pyresponse_type); + nb::tuple + take_response(nb::object pyresponse_type); /// Get rcl_client_t pointer rcl_client_t * @@ -101,7 +104,7 @@ class Client : public Destroyable, public std::enable_shared_from_this */ void configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Force an early destruction of this object @@ -137,12 +140,12 @@ class Client : public Destroyable, public std::enable_shared_from_this set_callback(rcl_event_callback_t callback, const void * user_data); }; -/// Define a pybind11 wrapper for an rclpy::Client +/// Define a nanobind wrapper for an rclpy::Client /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_client(py::object module); +define_client(nb::object module); } // namespace rclpy #endif // RCLPY__CLIENT_HPP_ diff --git a/rclpy/src/rclpy/clock.cpp b/rclpy/src/rclpy/clock.cpp index 4479c2f9a..88c7ed7d0 100644 --- a/rclpy/src/rclpy/clock.cpp +++ b/rclpy/src/rclpy/clock.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -23,7 +24,7 @@ #include "clock.hpp" -using pybind11::literals::operator""_a; +using nb::literals::operator""_a; namespace rclpy { @@ -91,7 +92,7 @@ void Clock::set_ros_time_override_is_enabled(bool enabled) } if (PyErr_Occurred()) { // Time jump callbacks raised - throw py::error_already_set(); + throw nb::python_error(); } } @@ -104,7 +105,7 @@ void Clock::set_ros_time_override(rcl_time_point_t time_point) if (PyErr_Occurred()) { // Time jump callbacks raised - throw py::error_already_set(); + throw nb::python_error(); } } @@ -120,29 +121,30 @@ _rclpy_on_time_jump( return; } auto pyjump_handle_c = static_cast(user_data); - auto pyjump_handle = py::reinterpret_borrow(pyjump_handle_c); + auto pyjump_handle = nb::borrow(pyjump_handle_c); if (before_jump) { // Call pre jump callback with no arguments - py::object pre_callback = pyjump_handle.attr("_pre_callback"); + nb::object pre_callback = pyjump_handle.attr("_pre_callback"); if (pre_callback.is_none()) { return; } pre_callback(); } else { // Call post jump callback with JumpInfo as an argument - py::object post_callback = pyjump_handle.attr("_post_callback"); + nb::object post_callback = pyjump_handle.attr("_post_callback"); if (post_callback.is_none()) { return; } - py::object clock_change = py::cast(time_jump->clock_change); + nb::object clock_change = nb::cast(time_jump->clock_change); + nb::object pydict = nb::module_::import_("builtins").attr("dict"); post_callback( - py::dict("clock_change"_a = clock_change, "delta"_a = time_jump->delta.nanoseconds)); + pydict("clock_change"_a = clock_change, "delta"_a = time_jump->delta.nanoseconds)); } } void Clock::add_clock_callback( - py::object pyjump_handle, + nb::object pyjump_handle, bool on_clock_change, int64_t min_forward, int64_t min_backward) @@ -159,7 +161,7 @@ void Clock::add_clock_callback( } } -void Clock::remove_clock_callback(py::object pyjump_handle) +void Clock::remove_clock_callback(nb::object pyjump_handle) { rcl_ret_t ret = rcl_clock_remove_jump_callback( rcl_clock_.get(), _rclpy_on_time_jump, pyjump_handle.ptr()); @@ -168,11 +170,11 @@ void Clock::remove_clock_callback(py::object pyjump_handle) } } -void define_clock(py::object module) +void define_clock(nb::object module) { - py::class_>(module, "Clock") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Clock") + .def(nb::init()) + .def_prop_ro( "pointer", [](const Clock & clock) { return reinterpret_cast(clock.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/clock.hpp b/rclpy/src/rclpy/clock.hpp index 53d0e0f4c..c4ff0631b 100644 --- a/rclpy/src/rclpy/clock.hpp +++ b/rclpy/src/rclpy/clock.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__CLOCK_HPP_ #define RCLPY__CLOCK_HPP_ -#include +#include +#include #include @@ -25,7 +26,7 @@ #include "exceptions.hpp" #include "utils.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -89,7 +90,7 @@ class Clock : public Destroyable, public std::enable_shared_from_this */ void add_clock_callback( - py::object pyjump_handle, + nb::object pyjump_handle, bool on_clock_change, int64_t min_forward, int64_t min_backward); @@ -101,7 +102,7 @@ class Clock : public Destroyable, public std::enable_shared_from_this * \param[in] pyjump_handle Instance of rclpy.clock.JumpHandle */ void - remove_clock_callback(py::object pyjump_handle); + remove_clock_callback(nb::object pyjump_handle); /// Get rcl_clock_t pointer rcl_clock_t * rcl_ptr() const @@ -116,8 +117,8 @@ class Clock : public Destroyable, public std::enable_shared_from_this std::shared_ptr rcl_clock_; }; -/// Define a pybind11 wrapper for an rclpy::Clock -void define_clock(py::object module); +/// Define a nanobind wrapper for an rclpy::Clock +void define_clock(nb::object module); } // namespace rclpy #endif // RCLPY__CLOCK_HPP_ diff --git a/rclpy/src/rclpy/clock_event.cpp b/rclpy/src/rclpy/clock_event.cpp index 71056dee3..4231f2730 100644 --- a/rclpy/src/rclpy/clock_event.cpp +++ b/rclpy/src/rclpy/clock_event.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -24,7 +25,7 @@ #include "clock_event.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -48,7 +49,7 @@ void ClockEvent::wait_until(std::shared_ptr clock, rcl_time_point_t until std::chrono::nanoseconds(delta_t.nanoseconds)); // Could be a long wait, release the gil - py::gil_scoped_release release; + nb::gil_scoped_release release; std::unique_lock lock(mutex_); cv_.wait_until(lock, chrono_until, [this]() {return state_;}); } @@ -58,7 +59,7 @@ void ClockEvent::wait_until_ros(std::shared_ptr clock, rcl_time_point_t u // Check if ROS time is enabled in C++ to avoid TOCTTOU with TimeSource by holding GIL if (clock->get_ros_time_override_is_enabled()) { // Could be a long wait, release the gil - py::gil_scoped_release release; + nb::gil_scoped_release release; std::unique_lock lock(mutex_); // Caller must have setup a time jump callback to wake this event cv_.wait(lock, [this]() {return state_;}); @@ -92,10 +93,10 @@ void ClockEvent::clear() cv_.notify_all(); } -void define_clock_event(py::object module) +void define_clock_event(nb::object module) { - py::class_(module, "ClockEvent") - .def(py::init()) + nb::class_(module, "ClockEvent") + .def(nb::init<>()) .def( "wait_until_steady", &ClockEvent::wait_until, "Wait for the event to be set (monotonic wait)") diff --git a/rclpy/src/rclpy/clock_event.hpp b/rclpy/src/rclpy/clock_event.hpp index d567fd933..7fc79c56e 100644 --- a/rclpy/src/rclpy/clock_event.hpp +++ b/rclpy/src/rclpy/clock_event.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__CLOCK_EVENT_HPP_ #define RCLPY__CLOCK_EVENT_HPP_ -#include +#include +#include #include @@ -25,7 +26,7 @@ #include "clock.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -62,8 +63,8 @@ class ClockEvent std::condition_variable cv_; }; -/// Define a pybind11 wrapper for an rclpy::ClockEvent -void define_clock_event(py::object module); +/// Define a nanobind wrapper for an rclpy::ClockEvent +void define_clock_event(nb::object module); } // namespace rclpy #endif // RCLPY__CLOCK_EVENT_HPP_ diff --git a/rclpy/src/rclpy/context.cpp b/rclpy/src/rclpy/context.cpp index e6b7166fc..320f46a0e 100644 --- a/rclpy/src/rclpy/context.cpp +++ b/rclpy/src/rclpy/context.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -52,7 +54,7 @@ void shutdown_contexts() g_contexts.clear(); } -Context::Context(py::list pyargs, size_t domain_id) +Context::Context(nb::list pyargs, size_t domain_id) { rcl_context_ = std::shared_ptr( new rcl_context_t, @@ -94,7 +96,7 @@ Context::Context(py::list pyargs, size_t domain_id) // CPython owns const char * memory - no need to free it arg_c_values[i] = PyUnicode_AsUTF8(pyargs[i].ptr()); if (!arg_c_values[i]) { - throw py::error_already_set(); + throw nb::python_error(); } } @@ -165,11 +167,11 @@ Context::shutdown() } } -void define_context(py::object module) +void define_context(nb::object module) { - py::class_>(module, "Context") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Context") + .def(nb::init()) + .def_prop_ro( "pointer", [](const Context & context) { return reinterpret_cast(context.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/context.hpp b/rclpy/src/rclpy/context.hpp index 30259e3e4..5710eb567 100644 --- a/rclpy/src/rclpy/context.hpp +++ b/rclpy/src/rclpy/context.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__CONTEXT_HPP_ #define RCLPY__CONTEXT_HPP_ -#include +#include +#include #include #include @@ -26,7 +27,7 @@ #include "destroyable.hpp" #include "exceptions.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -70,7 +71,7 @@ class Context : public Destroyable, public std::enable_shared_from_this * \param[in] pyargs List of command line arguments * \param[in] domain_id domain id to be set in this context */ - Context(py::list pyargs, size_t domain_id); + Context(nb::list pyargs, size_t domain_id); /// Retrieves domain id from init_options of context /** @@ -104,8 +105,8 @@ class Context : public Destroyable, public std::enable_shared_from_this bool already_shutdown_{false}; }; -/// Define a pybind11 wrapper for an rclpy::Context -void define_context(py::object module); +/// Define a nanobind wrapper for an rclpy::Context +void define_context(nb::object module); } // namespace rclpy #endif // RCLPY__CONTEXT_HPP_ diff --git a/rclpy/src/rclpy/destroyable.cpp b/rclpy/src/rclpy/destroyable.cpp index c981ac791..45e186654 100644 --- a/rclpy/src/rclpy/destroyable.cpp +++ b/rclpy/src/rclpy/destroyable.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -40,7 +41,7 @@ Destroyable::enter() } void -Destroyable::exit(py::object, py::object, py::object) +Destroyable::exit(nb::object, nb::object, nb::object) { if (0u == use_count) { throw std::runtime_error("Internal error: Destroyable use_count would be negative"); @@ -55,7 +56,7 @@ Destroyable::exit(py::object, py::object, py::object) void Destroyable::destroy() { - // Normally would be pure virtual, but then pybind11 can't create bindings for this class + // Normally would be pure virtual, but then nanobind can't create bindings for this class throw NotImplementedError("Internal error: Destroyable subclass didn't override destroy()"); } @@ -73,11 +74,11 @@ Destroyable::destroy_when_not_in_use() } void -define_destroyable(py::object module) +define_destroyable(nb::object module) { - py::class_>(module, "Destroyable") + nb::class_(module, "Destroyable") .def("__enter__", &Destroyable::enter) - .def("__exit__", &Destroyable::exit) + .def("__exit__", &Destroyable::exit, nb::arg().none(), nb::arg().none(), nb::arg().none()) .def( "destroy_when_not_in_use", &Destroyable::destroy_when_not_in_use, "Forcefully destroy the rcl object as soon as it's not actively being used"); diff --git a/rclpy/src/rclpy/destroyable.hpp b/rclpy/src/rclpy/destroyable.hpp index 92d6b0ad3..830a43af9 100644 --- a/rclpy/src/rclpy/destroyable.hpp +++ b/rclpy/src/rclpy/destroyable.hpp @@ -15,9 +15,9 @@ #ifndef RCLPY__DESTROYABLE_HPP_ #define RCLPY__DESTROYABLE_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -37,7 +37,7 @@ class Destroyable /// Context manager __exit__ - unblock destruction void - exit(py::object pytype, py::object pyvalue, py::object pytraceback); + exit(nb::object pytype, nb::object pyvalue, nb::object pytraceback); /// Signal that the object should be destroyed as soon as it's not in use void @@ -56,11 +56,11 @@ class Destroyable bool please_destroy_ = false; }; -/// Define a pybind11 wrapper for an rclpy::Destroyable +/// Define a nanobind wrapper for an rclpy::Destroyable /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ -void define_destroyable(py::object module); +void define_destroyable(nb::object module); } // namespace rclpy #endif // RCLPY__DESTROYABLE_HPP_ diff --git a/rclpy/src/rclpy/duration.cpp b/rclpy/src/rclpy/duration.cpp index 6554c42e8..8f4760be4 100644 --- a/rclpy/src/rclpy/duration.cpp +++ b/rclpy/src/rclpy/duration.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include @@ -28,10 +28,10 @@ create_duration(int64_t nanoseconds) return duration; } void -define_duration(py::object module) +define_duration(nb::object module) { - py::class_(module, "rcl_duration_t") - .def(py::init<>(&create_duration)) - .def_readonly("nanoseconds", &rcl_duration_t::nanoseconds); + nb::class_(module, "rcl_duration_t") + .def(nb::new_(&create_duration)) + .def_ro("nanoseconds", &rcl_duration_t::nanoseconds); } } // namespace rclpy diff --git a/rclpy/src/rclpy/duration.hpp b/rclpy/src/rclpy/duration.hpp index a22855698..9f419ed75 100644 --- a/rclpy/src/rclpy/duration.hpp +++ b/rclpy/src/rclpy/duration.hpp @@ -15,17 +15,17 @@ #ifndef RCLPY__DURATION_HPP_ #define RCLPY__DURATION_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { -/// Define a pybind11 wrapper for an rcl_duration_t +/// Define a nanobind wrapper for an rcl_duration_t /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ -void define_duration(py::object module); +void define_duration(nb::object module); } // namespace rclpy #endif // RCLPY__DURATION_HPP_ diff --git a/rclpy/src/rclpy/event_handle.cpp b/rclpy/src/rclpy/event_handle.cpp index 78dc3ec96..dcb58bf9e 100644 --- a/rclpy/src/rclpy/event_handle.cpp +++ b/rclpy/src/rclpy/event_handle.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -114,7 +115,7 @@ typedef union event_callback_data { rmw_incompatible_type_status_t incompatible_type; } event_callback_data_t; -py::object +nb::object EventHandle::take_event() { event_callback_data_t data; @@ -124,7 +125,7 @@ EventHandle::take_event() throw std::bad_alloc(); } if (RCL_RET_EVENT_TAKE_FAILED == ret) { - return py::none(); + return nb::none(); } if (RCL_RET_OK != ret) { throw RCLError("failed to take event"); @@ -133,30 +134,30 @@ EventHandle::take_event() if (auto sub_type = std::get_if(&event_type_)) { switch (*sub_type) { case RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED: - return py::cast(data.requested_deadline_missed); + return nb::cast(data.requested_deadline_missed); case RCL_SUBSCRIPTION_LIVELINESS_CHANGED: - return py::cast(data.liveliness_changed); + return nb::cast(data.liveliness_changed); case RCL_SUBSCRIPTION_MESSAGE_LOST: - return py::cast(data.message_lost); + return nb::cast(data.message_lost); case RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS: - return py::cast(data.requested_incompatible_qos); + return nb::cast(data.requested_incompatible_qos); case RCL_SUBSCRIPTION_INCOMPATIBLE_TYPE: - return py::cast(data.incompatible_type); + return nb::cast(data.incompatible_type); case RCL_SUBSCRIPTION_MATCHED: - return py::cast(data.subscription_matched); + return nb::cast(data.subscription_matched); } } else if (auto pub_type = std::get_if(&event_type_)) { switch (*pub_type) { case RCL_PUBLISHER_OFFERED_DEADLINE_MISSED: - return py::cast(data.offered_deadline_missed); + return nb::cast(data.offered_deadline_missed); case RCL_PUBLISHER_LIVELINESS_LOST: - return py::cast(data.liveliness_lost); + return nb::cast(data.liveliness_lost); case RCL_PUBLISHER_OFFERED_INCOMPATIBLE_QOS: - return py::cast(data.offered_incompatible_qos); + return nb::cast(data.offered_incompatible_qos); case RCL_PUBLISHER_INCOMPATIBLE_TYPE: - return py::cast(data.incompatible_type); + return nb::cast(data.incompatible_type); case RCL_PUBLISHER_MATCHED: - return py::cast(data.publisher_matched); + return nb::cast(data.publisher_matched); } } throw std::runtime_error("cannot take event that is neither a publisher or a subscription event"); @@ -175,12 +176,12 @@ subscription_event_type_is_supported(rcl_subscription_event_type_t event_type) } void -define_event_handle(py::module module) +define_event_handle(nb::module_ module) { - py::class_>(module, "EventHandle") - .def(py::init()) - .def(py::init()) - .def_property_readonly( + nb::class_(module, "EventHandle") + .def(nb::init()) + .def(nb::init()) + .def_prop_ro( "pointer", [](const EventHandle & event) { return reinterpret_cast(event.rcl_ptr()); }, @@ -189,7 +190,8 @@ define_event_handle(py::module module) "take_event", &EventHandle::take_event, "Get pending data from a ready event"); - py::enum_(module, "rcl_subscription_event_type_t") + nb::enum_( + module, "rcl_subscription_event_type_t", nb::is_arithmetic()) .value("RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED", RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED) .value("RCL_SUBSCRIPTION_LIVELINESS_CHANGED", RCL_SUBSCRIPTION_LIVELINESS_CHANGED) .value("RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS", RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS) @@ -197,58 +199,59 @@ define_event_handle(py::module module) .value("RCL_SUBSCRIPTION_INCOMPATIBLE_TYPE", RCL_SUBSCRIPTION_INCOMPATIBLE_TYPE) .value("RCL_SUBSCRIPTION_MATCHED", RCL_SUBSCRIPTION_MATCHED); - py::enum_(module, "rcl_publisher_event_type_t") + nb::enum_( + module, "rcl_publisher_event_type_t", nb::is_arithmetic()) .value("RCL_PUBLISHER_OFFERED_DEADLINE_MISSED", RCL_PUBLISHER_OFFERED_DEADLINE_MISSED) .value("RCL_PUBLISHER_LIVELINESS_LOST", RCL_PUBLISHER_LIVELINESS_LOST) .value("RCL_PUBLISHER_OFFERED_INCOMPATIBLE_QOS", RCL_PUBLISHER_OFFERED_INCOMPATIBLE_QOS) .value("RCL_PUBLISHER_INCOMPATIBLE_TYPE", RCL_PUBLISHER_INCOMPATIBLE_TYPE) .value("RCL_PUBLISHER_MATCHED", RCL_PUBLISHER_MATCHED); - py::class_( + nb::class_( module, "rmw_requested_deadline_missed_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_requested_deadline_missed_status_t::total_count) - .def_readonly("total_count_change", &rmw_requested_deadline_missed_status_t::total_count_change); + .def(nb::init<>()) + .def_ro("total_count", &rmw_requested_deadline_missed_status_t::total_count) + .def_ro("total_count_change", &rmw_requested_deadline_missed_status_t::total_count_change); - py::class_(module, "rmw_liveliness_changed_status_t") - .def(py::init<>()) - .def_readonly("alive_count", &rmw_liveliness_changed_status_t::alive_count) - .def_readonly("not_alive_count", &rmw_liveliness_changed_status_t::not_alive_count) - .def_readonly("alive_count_change", &rmw_liveliness_changed_status_t::alive_count_change) - .def_readonly("not_alive_count_change", &rmw_liveliness_changed_status_t::not_alive_count_change); + nb::class_(module, "rmw_liveliness_changed_status_t") + .def(nb::init<>()) + .def_ro("alive_count", &rmw_liveliness_changed_status_t::alive_count) + .def_ro("not_alive_count", &rmw_liveliness_changed_status_t::not_alive_count) + .def_ro("alive_count_change", &rmw_liveliness_changed_status_t::alive_count_change) + .def_ro("not_alive_count_change", &rmw_liveliness_changed_status_t::not_alive_count_change); - py::class_(module, "rmw_message_lost_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_message_lost_status_t::total_count) - .def_readonly("total_count_change", &rmw_message_lost_status_t::total_count_change); + nb::class_(module, "rmw_message_lost_status_t") + .def(nb::init<>()) + .def_ro("total_count", &rmw_message_lost_status_t::total_count) + .def_ro("total_count_change", &rmw_message_lost_status_t::total_count_change); - py::class_( + nb::class_( module, "rmw_requested_qos_incompatible_event_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_requested_qos_incompatible_event_status_t::total_count) - .def_readonly( + .def(nb::init<>()) + .def_ro("total_count", &rmw_requested_qos_incompatible_event_status_t::total_count) + .def_ro( "total_count_change", &rmw_requested_qos_incompatible_event_status_t::total_count_change) - .def_readonly( + .def_ro( "last_policy_kind", &rmw_requested_qos_incompatible_event_status_t::last_policy_kind); - py::class_(module, "rmw_offered_deadline_missed_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_offered_deadline_missed_status_t::total_count) - .def_readonly("total_count_change", &rmw_offered_deadline_missed_status_t::total_count_change); + nb::class_(module, "rmw_offered_deadline_missed_status_t") + .def(nb::init<>()) + .def_ro("total_count", &rmw_offered_deadline_missed_status_t::total_count) + .def_ro("total_count_change", &rmw_offered_deadline_missed_status_t::total_count_change); - py::class_(module, "rmw_liveliness_lost_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_liveliness_lost_status_t::total_count) - .def_readonly("total_count_change", &rmw_liveliness_lost_status_t::total_count_change); + nb::class_(module, "rmw_liveliness_lost_status_t") + .def(nb::init<>()) + .def_ro("total_count", &rmw_liveliness_lost_status_t::total_count) + .def_ro("total_count_change", &rmw_liveliness_lost_status_t::total_count_change); - py::class_(module, "rmw_matched_status_t") - .def(py::init<>()) - .def_readonly("total_count", &rmw_matched_status_t::total_count) - .def_readonly("total_count_change", &rmw_matched_status_t::total_count_change) - .def_readonly("current_count", &rmw_matched_status_t::current_count) - .def_readonly("current_count_change", &rmw_matched_status_t::current_count_change); + nb::class_(module, "rmw_matched_status_t") + .def(nb::init<>()) + .def_ro("total_count", &rmw_matched_status_t::total_count) + .def_ro("total_count_change", &rmw_matched_status_t::total_count_change) + .def_ro("current_count", &rmw_matched_status_t::current_count) + .def_ro("current_count_change", &rmw_matched_status_t::current_count_change); - py::enum_(module, "rmw_qos_policy_kind_t") + nb::enum_(module, "rmw_qos_policy_kind_t", nb::is_arithmetic()) .value("RMW_QOS_POLICY_INVALID", RMW_QOS_POLICY_INVALID) .value("RMW_QOS_POLICY_DURABILITY", RMW_QOS_POLICY_DURABILITY) .value("RMW_QOS_POLICY_DEADLINE", RMW_QOS_POLICY_DEADLINE) @@ -262,9 +265,9 @@ define_event_handle(py::module module) "RMW_QOS_POLICY_AVOID_ROS_NAMESPACE_CONVENTIONS", RMW_QOS_POLICY_AVOID_ROS_NAMESPACE_CONVENTIONS); - py::class_(module, "rmw_incompatible_type_status_t") - .def(py::init<>()) - .def_readonly("total_count_change", &rmw_incompatible_type_status_t::total_count_change); + nb::class_(module, "rmw_incompatible_type_status_t") + .def(nb::init<>()) + .def_ro("total_count_change", &rmw_incompatible_type_status_t::total_count_change); module.def( "publisher_event_type_is_supported", diff --git a/rclpy/src/rclpy/event_handle.hpp b/rclpy/src/rclpy/event_handle.hpp index 432e2a248..3c7ffdf28 100644 --- a/rclpy/src/rclpy/event_handle.hpp +++ b/rclpy/src/rclpy/event_handle.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__EVENT_HANDLE_HPP_ #define RCLPY__EVENT_HANDLE_HPP_ -#include +#include +#include #include @@ -26,7 +27,7 @@ #include "publisher.hpp" #include "subscription.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -75,7 +76,7 @@ class EventHandle : public Destroyable, public std::enable_shared_from_this rcl_event_; }; -/// Define a pybind11 wrapper for an rclpy::EventHandle +/// Define a nanobind wrapper for an rclpy::EventHandle /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_event_handle(py::module module); +define_event_handle(nb::module_ module); } // namespace rclpy #endif // RCLPY__EVENT_HANDLE_HPP_ diff --git a/rclpy/src/rclpy/events_executor/events_executor.cpp b/rclpy/src/rclpy/events_executor/events_executor.cpp index 7c6bd229c..78fff8a0b 100644 --- a/rclpy/src/rclpy/events_executor/events_executor.cpp +++ b/rclpy/src/rclpy/events_executor/events_executor.cpp @@ -24,9 +24,7 @@ // limitations under the License. #include "events_executor/events_executor.hpp" -#include -#include -#include +#include #include #include @@ -40,20 +38,21 @@ #include "subscription.hpp" namespace pl = std::placeholders; -namespace py = pybind11; +namespace nb = nanobind; +using nb::literals::operator""_a; namespace rclpy { namespace events_executor { -EventsExecutor::EventsExecutor(py::object context) +EventsExecutor::EventsExecutor(nb::object context) : rclpy_context_(context), - inspect_iscoroutine_(py::module_::import("inspect").attr("iscoroutine")), - inspect_signature_(py::module_::import("inspect").attr("signature")), - rclpy_task_(py::module_::import("rclpy.task").attr("Task")), - rclpy_future_(py::module_::import("rclpy.task").attr("Future")), - rclpy_timer_timer_info_(py::module_::import("rclpy.timer").attr("TimerInfo")), + inspect_iscoroutine_(nb::module_::import_("inspect").attr("iscoroutine")), + inspect_signature_(nb::module_::import_("inspect").attr("signature")), + rclpy_task_(nb::module_::import_("rclpy.task").attr("Task")), + rclpy_future_(nb::module_::import_("rclpy.task").attr("Future")), + rclpy_timer_timer_info_(nb::module_::import_("rclpy.timer").attr("TimerInfo")), signal_callback_([this]() {events_queue_.Stop();}), rcl_callback_manager_(&events_queue_), timers_manager_( @@ -63,31 +62,29 @@ EventsExecutor::EventsExecutor(py::object context) EventsExecutor::~EventsExecutor() {shutdown();} -pybind11::object EventsExecutor::create_task( - py::object callback, py::args args, const py::kwargs & kwargs) +nb::object EventsExecutor::create_task( + nb::object callback, nb::args args, const nb::kwargs & kwargs) { // Create and return a rclpy.task.Task() object, and schedule it to be called later. - using py::literals::operator""_a; - py::object task = rclpy_task_(callback, args, kwargs, "executor"_a = py::cast(this)); + nb::object task = rclpy_task_(callback, args, kwargs, "executor"_a = nb::cast(this)); // The Task needs to be owned at least until we invoke it from the callback we post, however we - // can't pass a bare py::object because that's going to try to do Python refcounting while + // can't pass a bare nb::object because that's going to try to do Python refcounting while // preparing to go into or coming back from the callback, while the GIL is not held. We'll do // manual refcounting on it instead. - py::handle cb_task_handle = task; + nb::handle cb_task_handle = task; cb_task_handle.inc_ref(); call_task_in_next_spin(task); return task; } -void EventsExecutor::call_task_in_next_spin(pybind11::handle task) +void EventsExecutor::call_task_in_next_spin(nb::handle task) { events_queue_.Enqueue(std::bind(&EventsExecutor::IterateTask, this, task)); } -pybind11::object EventsExecutor::create_future() +nb::object EventsExecutor::create_future() { - using py::literals::operator""_a; - return rclpy_future_("executor"_a = py::cast(this)); + return rclpy_future_("executor"_a = nb::cast(this)); } bool EventsExecutor::shutdown(std::optional timeout) @@ -100,7 +97,7 @@ bool EventsExecutor::shutdown(std::optional timeout) // Block until spinning is done, or timeout. Release the GIL while we block though. { - py::gil_scoped_release gil_release; + nb::gil_scoped_release gil_release; std::unique_lock spin_lock(spinning_mutex_, std::defer_lock); if (timeout) { if (!spin_lock.try_lock_for(std::chrono::duration(*timeout))) { @@ -112,14 +109,14 @@ bool EventsExecutor::shutdown(std::optional timeout) } // Tear down any callbacks we still have registered. - for (py::handle node : py::list(nodes_)) { + for (nb::handle node : nb::list(nodes_)) { remove_node(node); } UpdateEntitiesFromNodes(true); return true; } -bool EventsExecutor::add_node(py::object node) +bool EventsExecutor::add_node(nb::object node) { if (nodes_.contains(node)) { return false; @@ -127,17 +124,17 @@ bool EventsExecutor::add_node(py::object node) nodes_.add(node); // Caution, the Node executor setter method calls executor.add_node() again making this // reentrant. - node.attr("executor") = py::cast(this); + node.attr("executor") = nb::cast(this); wake(); return true; } -void EventsExecutor::remove_node(py::handle node) +void EventsExecutor::remove_node(nb::handle node) { if (!nodes_.contains(node)) { return; } - // Why does pybind11 provide a C++ method for add() but not discard() or remove()? + // Why does nanobind provide a C++ method for add() but not discard() or remove()? nodes_.attr("remove")(node); // Not sure why rclpy doesn't change the node.executor at this point wake(); @@ -148,13 +145,13 @@ void EventsExecutor::wake() if (!wake_pending_.exchange(true)) { // Update tracked entities. events_queue_.Enqueue([this]() { - py::gil_scoped_acquire gil_acquire; - UpdateEntitiesFromNodes(!py::cast(rclpy_context_.attr("ok")())); + nb::gil_scoped_acquire gil_acquire; + UpdateEntitiesFromNodes(!nb::cast(rclpy_context_.attr("ok")())); }); } } -py::list EventsExecutor::get_nodes() const {return nodes_;} +nb::list EventsExecutor::get_nodes() const {return nb::list(nodes_);} // NOTE: The timeouts on the below two methods are always realtime even if we're running in debug // time. This is true of other executors too, because debug time is always associated with a @@ -171,7 +168,7 @@ void EventsExecutor::spin(std::optional timeout_sec, bool stop_after_use stop_after_user_callback_ = stop_after_user_callback; // Release the GIL while we block. Any callbacks on the events queue that want to touch Python // will need to reacquire it though. - py::gil_scoped_release gil_release; + nb::gil_scoped_release gil_release; if (timeout_sec) { const auto timeout_ns = std::chrono::duration_cast( std::chrono::duration(*timeout_sec)); @@ -183,16 +180,16 @@ void EventsExecutor::spin(std::optional timeout_sec, bool stop_after_use events_queue_.Restart(); } - const bool ok = py::cast(rclpy_context_.attr("ok")()); + const bool ok = nb::cast(rclpy_context_.attr("ok")()); if (!ok) { - Raise(py::module_::import("rclpy.executors").attr("ExternalShutdownException")()); + Raise(nb::module_::import_("rclpy.executors").attr("ExternalShutdownException")()); } } void EventsExecutor::spin_until_future_complete( - py::handle future, std::optional timeout_sec, bool stop_after_user_callback) + nb::handle future, std::optional timeout_sec, bool stop_after_user_callback) { - py::cpp_function cb([this](py::handle) {events_queue_.Stop();}); + nb::object cb = nb::cpp_function([this](nb::handle) {events_queue_.Stop();}); future.attr("add_done_callback")(cb); spin(timeout_sec, stop_after_user_callback); // In case the future didn't complete (we hit the timeout or dispatched a different user callback @@ -204,7 +201,7 @@ void EventsExecutor::spin_until_future_complete( } EventsExecutor * EventsExecutor::enter() {return this;} -void EventsExecutor::exit(py::object, py::object, py::object) {shutdown();} +void EventsExecutor::exit(nb::object, nb::object, nb::object) {shutdown();} void EventsExecutor::UpdateEntitiesFromNodes(bool shutdown) { @@ -213,22 +210,22 @@ void EventsExecutor::UpdateEntitiesFromNodes(bool shutdown) wake_pending_.store(false); // Collect all entities currently associated with our nodes - py::set subscriptions; - py::set timers; - py::set clients; - py::set services; - py::set waitables; + nb::set subscriptions; + nb::set timers; + nb::set clients; + nb::set services; + nb::set waitables; if (!shutdown) { - for (py::handle node : nodes_) { - subscriptions.attr("update")(py::set(node.attr("subscriptions"))); - timers.attr("update")(py::set(node.attr("timers"))); - clients.attr("update")(py::set(node.attr("clients"))); - services.attr("update")(py::set(node.attr("services"))); - waitables.attr("update")(py::set(node.attr("waitables"))); + for (nb::handle node : nodes_) { + subscriptions.attr("update")(nb::set(nb::object(node.attr("subscriptions")))); + timers.attr("update")(nb::set(nb::object(node.attr("timers")))); + clients.attr("update")(nb::set(nb::object(node.attr("clients")))); + services.attr("update")(nb::set(nb::object(node.attr("services")))); + waitables.attr("update")(nb::set(nb::object(node.attr("waitables")))); // It doesn't seem to be possible to support guard conditions with a callback-based (as // opposed to waitset-based) API. Fortunately we don't seem to need to. - if (!py::set(node.attr("guards")).empty()) { + if (!nb::set(nb::object(node.attr("guards"))).empty()) { throw std::runtime_error("Guard conditions not supported"); } } @@ -262,28 +259,28 @@ void EventsExecutor::UpdateEntitiesFromNodes(bool shutdown) } void EventsExecutor::UpdateEntitySet( - py::set & entity_set, const py::set & new_entity_set, - std::function added_entity_callback, - std::function removed_entity_callback) + nb::set & entity_set, const nb::set & new_entity_set, + std::function added_entity_callback, + std::function removed_entity_callback) { - py::set added_entities = new_entity_set - entity_set; - for (py::handle added_entity : added_entities) { + nb::set added_entities(new_entity_set - entity_set); + for (nb::handle added_entity : added_entities) { added_entity_callback(added_entity); } - py::set removed_entities = entity_set - new_entity_set; - for (py::handle removed_entity : removed_entities) { + nb::set removed_entities(entity_set - new_entity_set); + for (nb::handle removed_entity : removed_entities) { removed_entity_callback(removed_entity); } entity_set = new_entity_set; } -void EventsExecutor::HandleAddedSubscription(py::handle subscription) +void EventsExecutor::HandleAddedSubscription(nb::handle subscription) { - py::handle handle = subscription.attr("handle"); + nb::handle handle = subscription.attr("handle"); auto with = std::make_shared(handle); - const rcl_subscription_t * rcl_ptr = py::cast(handle).rcl_ptr(); + const rcl_subscription_t * rcl_ptr = nb::cast(handle).rcl_ptr(); const auto cb = std::bind(&EventsExecutor::HandleSubscriptionReady, this, subscription, pl::_1); if ( RCL_RET_OK != rcl_subscription_set_on_new_message_callback( @@ -296,10 +293,10 @@ void EventsExecutor::HandleAddedSubscription(py::handle subscription) } } -void EventsExecutor::HandleRemovedSubscription(py::handle subscription) +void EventsExecutor::HandleRemovedSubscription(nb::handle subscription) { - py::handle handle = subscription.attr("handle"); - const rcl_subscription_t * rcl_ptr = py::cast(handle).rcl_ptr(); + nb::handle handle = subscription.attr("handle"); + const rcl_subscription_t * rcl_ptr = nb::cast(handle).rcl_ptr(); if (RCL_RET_OK != rcl_subscription_set_on_new_message_callback(rcl_ptr, nullptr, nullptr)) { throw std::runtime_error( std::string("Failed to clear the on new message callback for subscription: ") + @@ -308,22 +305,22 @@ void EventsExecutor::HandleRemovedSubscription(py::handle subscription) rcl_callback_manager_.RemoveCallback(rcl_ptr); } -void EventsExecutor::HandleSubscriptionReady(py::handle subscription, size_t number_of_events) +void EventsExecutor::HandleSubscriptionReady(nb::handle subscription, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // Largely based on rclpy.Executor._take_subscription() and _execute_subscription(). // https://github.com/ros2/rclpy/blob/06d78fb28a6d61ede793201ae75474f3e5432b47/rclpy/rclpy/executors.py#L355-L367 // // NOTE: Simple object attributes we can count on to be owned by the parent object, but bound // method calls and function return values need to be owned by us. - Subscription & _rclpy_sub = py::cast(subscription.attr("handle")); - const py::object msg_type = subscription.attr("msg_type"); - const bool raw = py::cast(subscription.attr("raw")); - const int callback_type = py::cast(subscription.attr("_callback_type").attr("value")); + Subscription & _rclpy_sub = nb::cast(subscription.attr("handle")); + const nb::object msg_type = subscription.attr("msg_type"); + const bool raw = nb::cast(subscription.attr("raw")); + const int callback_type = nb::cast(subscription.attr("_callback_type").attr("value")); const int message_only = - py::cast(subscription.attr("CallbackType").attr("MessageOnly").attr("value")); - const py::handle callback = subscription.attr("callback"); + nb::cast(subscription.attr("CallbackType").attr("MessageOnly").attr("value")); + const nb::handle callback = subscription.attr("callback"); // rmw_cyclonedds has a bug which causes number_of_events to be zero in the case where messages // were waiting for us when we registered the callback, and the topic is using KEEP_ALL history @@ -331,23 +328,23 @@ void EventsExecutor::HandleSubscriptionReady(py::handle subscription, size_t num // getting None in that case. https://github.com/ros2/rmw_cyclonedds/issues/509 bool got_none = false; for (size_t i = 0; number_of_events ? i < number_of_events : !got_none; ++i) { - py::object msg_info = _rclpy_sub.take_message(msg_type, raw); + nb::object msg_info = _rclpy_sub.take_message(msg_type, raw); if (!msg_info.is_none()) { - py::object result; + nb::object result; try { if (callback_type == message_only) { - result = callback(py::cast(msg_info)[0]); + result = callback(nb::cast(msg_info)[0]); } else { result = callback(msg_info); } - } catch (const py::error_already_set & e) { + } catch (const nb::python_error & e) { HandleCallbackExceptionInNodeEntity(e, subscription, "subscriptions"); throw; } // The type markup claims the callback can't be a coroutine, but this seems to be a lie // because the stock executor handles it just fine. - if (py::cast(inspect_iscoroutine_(result))) { + if (nb::cast(inspect_iscoroutine_(result))) { // Create a Task to manage iteration of this coroutine later. create_task(result); } else if (stop_after_user_callback_) { @@ -359,40 +356,39 @@ void EventsExecutor::HandleSubscriptionReady(py::handle subscription, size_t num } } -void EventsExecutor::HandleAddedTimer(py::handle timer) {timers_manager_.AddTimer(timer);} +void EventsExecutor::HandleAddedTimer(nb::handle timer) {timers_manager_.AddTimer(timer);} -void EventsExecutor::HandleRemovedTimer(py::handle timer) {timers_manager_.RemoveTimer(timer);} +void EventsExecutor::HandleRemovedTimer(nb::handle timer) {timers_manager_.RemoveTimer(timer);} -void EventsExecutor::HandleTimerReady(py::handle timer, const rcl_timer_call_info_t & info) +void EventsExecutor::HandleTimerReady(nb::handle timer, const rcl_timer_call_info_t & info) { - py::gil_scoped_acquire gil_acquire; - py::object callback = timer.attr("callback"); + nb::gil_scoped_acquire gil_acquire; + nb::object callback = timer.attr("callback"); // We need to distinguish callbacks that want a TimerInfo object from those that don't. // Executor._take_timer() actually checks if an argument has type markup expecting a TypeInfo // object. This seems like overkill, vs just checking if it wants an argument at all? - py::object py_info; - if (py::len(inspect_signature_(callback).attr("parameters").attr("values")()) > 0) { - using py::literals::operator""_a; + nb::object py_info; + if (nb::len(inspect_signature_(callback).attr("parameters").attr("values")()) > 0) { py_info = rclpy_timer_timer_info_( "expected_call_time"_a = info.expected_call_time, "actual_call_time"_a = info.actual_call_time, "clock_type"_a = timer.attr("clock").attr("clock_type")); } - py::object result; + nb::object result; try { if (py_info) { result = callback(py_info); } else { result = callback(); } - } catch (const py::error_already_set & e) { + } catch (const nb::python_error & e) { HandleCallbackExceptionInNodeEntity(e, timer, "timers"); throw; } // The type markup claims the callback can't be a coroutine, but this seems to be a lie because // the unit test does exactly that. - if (py::cast(inspect_iscoroutine_(result))) { + if (nb::cast(inspect_iscoroutine_(result))) { // Create a Task to manage iteration of this coroutine later. create_task(result); } else if (stop_after_user_callback_) { @@ -400,11 +396,11 @@ void EventsExecutor::HandleTimerReady(py::handle timer, const rcl_timer_call_inf } } -void EventsExecutor::HandleAddedClient(py::handle client) +void EventsExecutor::HandleAddedClient(nb::handle client) { - py::handle handle = client.attr("handle"); + nb::handle handle = client.attr("handle"); auto with = std::make_shared(handle); - const rcl_client_t * rcl_ptr = py::cast(handle).rcl_ptr(); + const rcl_client_t * rcl_ptr = nb::cast(handle).rcl_ptr(); const auto cb = std::bind(&EventsExecutor::HandleClientReady, this, client, pl::_1); if ( RCL_RET_OK != rcl_client_set_on_new_response_callback( @@ -417,10 +413,10 @@ void EventsExecutor::HandleAddedClient(py::handle client) } } -void EventsExecutor::HandleRemovedClient(py::handle client) +void EventsExecutor::HandleRemovedClient(nb::handle client) { - py::handle handle = client.attr("handle"); - const rcl_client_t * rcl_ptr = py::cast(handle).rcl_ptr(); + nb::handle handle = client.attr("handle"); + const rcl_client_t * rcl_ptr = nb::cast(handle).rcl_ptr(); if (RCL_RET_OK != rcl_client_set_on_new_response_callback(rcl_ptr, nullptr, nullptr)) { throw std::runtime_error( std::string("Failed to clear the on new response callback for client: ") + @@ -429,40 +425,40 @@ void EventsExecutor::HandleRemovedClient(py::handle client) rcl_callback_manager_.RemoveCallback(rcl_ptr); } -void EventsExecutor::HandleClientReady(py::handle client, size_t number_of_events) +void EventsExecutor::HandleClientReady(nb::handle client, size_t number_of_events) { if (stop_after_user_callback_) { events_queue_.Stop(); } - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // Largely based on rclpy.Executor._take_client() and _execute_client(). // https://github.com/ros2/rclpy/blob/06d78fb28a6d61ede793201ae75474f3e5432b47/rclpy/rclpy/executors.py#L369-L384 - Client & _rclpy_client = py::cast(client.attr("handle")); - const py::handle srv_type = client.attr("srv_type"); - const py::object res_type = srv_type.attr("Response"); - const py::object get_pending_request = client.attr("get_pending_request"); + Client & _rclpy_client = nb::cast(client.attr("handle")); + const nb::handle srv_type = client.attr("srv_type"); + const nb::object res_type = srv_type.attr("Response"); + const nb::object get_pending_request = client.attr("get_pending_request"); for (size_t i = 0; i < number_of_events; ++i) { - py::tuple seq_and_response = _rclpy_client.take_response(res_type); - py::handle header = seq_and_response[0]; - py::handle response = seq_and_response[1]; + nb::tuple seq_and_response = _rclpy_client.take_response(res_type); + nb::handle header = seq_and_response[0]; + nb::handle response = seq_and_response[1]; if (!header.is_none()) { - py::object sequence = header.attr("request_id").attr("sequence_number"); - py::object future; + nb::object sequence = header.attr("request_id").attr("sequence_number"); + nb::object future; try { future = get_pending_request(sequence); - } catch (const py::error_already_set & e) { + } catch (const nb::python_error & e) { if (e.matches(PyExc_KeyError)) { // The request was cancelled continue; } throw; } - future.attr("_set_executor")(py::cast(this)); + future.attr("_set_executor")(nb::cast(this)); try { future.attr("set_result")(response); - } catch (const py::error_already_set & e) { + } catch (const nb::python_error & e) { HandleCallbackExceptionInNodeEntity(e, client, "clients"); throw; } @@ -470,11 +466,11 @@ void EventsExecutor::HandleClientReady(py::handle client, size_t number_of_event } } -void EventsExecutor::HandleAddedService(py::handle service) +void EventsExecutor::HandleAddedService(nb::handle service) { - py::handle handle = service.attr("handle"); + nb::handle handle = service.attr("handle"); auto with = std::make_shared(handle); - const rcl_service_t * rcl_ptr = py::cast(handle).rcl_ptr(); + const rcl_service_t * rcl_ptr = nb::cast(handle).rcl_ptr(); const auto cb = std::bind(&EventsExecutor::HandleServiceReady, this, service, pl::_1); if ( RCL_RET_OK != rcl_service_set_on_new_request_callback( @@ -487,10 +483,10 @@ void EventsExecutor::HandleAddedService(py::handle service) } } -void EventsExecutor::HandleRemovedService(py::handle service) +void EventsExecutor::HandleRemovedService(nb::handle service) { - py::handle handle = service.attr("handle"); - const rcl_service_t * rcl_ptr = py::cast(handle).rcl_ptr(); + nb::handle handle = service.attr("handle"); + const rcl_service_t * rcl_ptr = nb::cast(handle).rcl_ptr(); if (RCL_RET_OK != rcl_service_set_on_new_request_callback(rcl_ptr, nullptr, nullptr)) { throw std::runtime_error( std::string("Failed to clear the on new request callback for service: ") + @@ -499,38 +495,38 @@ void EventsExecutor::HandleRemovedService(py::handle service) rcl_callback_manager_.RemoveCallback(rcl_ptr); } -void EventsExecutor::HandleServiceReady(py::handle service, size_t number_of_events) +void EventsExecutor::HandleServiceReady(nb::handle service, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // Largely based on rclpy.Executor._take_service() and _execute_service(). // https://github.com/ros2/rclpy/blob/06d78fb28a6d61ede793201ae75474f3e5432b47/rclpy/rclpy/executors.py#L386-L397 - Service & _rclpy_service = py::cast(service.attr("handle")); - const py::handle srv_type = service.attr("srv_type"); - const py::object req_type = srv_type.attr("Request"); - const py::handle res_type = srv_type.attr("Response"); - const py::handle callback = service.attr("callback"); - const py::object send_response = service.attr("send_response"); + Service & _rclpy_service = nb::cast(service.attr("handle")); + const nb::handle srv_type = service.attr("srv_type"); + const nb::object req_type = srv_type.attr("Request"); + const nb::handle res_type = srv_type.attr("Response"); + const nb::handle callback = service.attr("callback"); + const nb::object send_response = service.attr("send_response"); for (size_t i = 0; i < number_of_events; ++i) { - py::tuple request_and_header = _rclpy_service.service_take_request(req_type); - py::handle request = request_and_header[0]; - py::object header = request_and_header[1]; + nb::tuple request_and_header = _rclpy_service.service_take_request(req_type); + nb::handle request = request_and_header[0]; + nb::object header = request_and_header[1]; if (!request.is_none()) { - py::object response; + nb::object response; try { response = callback(request, res_type()); - } catch (const py::error_already_set & e) { + } catch (const nb::python_error & e) { HandleCallbackExceptionInNodeEntity(e, service, "services"); throw; } // The type markup claims the callback can't be a coroutine, but this seems to be a lie // because the stock executor handles it just fine. - if (py::cast(inspect_iscoroutine_(response))) { + if (nb::cast(inspect_iscoroutine_(response))) { // Create a Task to manage iteration of this coroutine later. create_task(response).attr("add_done_callback")( - py::cpp_function([send_response, header](py::object future) { + nb::cpp_function([send_response, header](nb::object future) { send_response(future.attr("result")(), header); })); } else { @@ -543,7 +539,7 @@ void EventsExecutor::HandleServiceReady(py::handle service, size_t number_of_eve } } -void EventsExecutor::HandleAddedWaitable(py::handle waitable) +void EventsExecutor::HandleAddedWaitable(nb::handle waitable) { // The Waitable API is too abstract for us to work with directly; it only exposes APIs for // dealing with wait sets, and all of the rcl callback API requires knowing exactly what kinds of @@ -552,18 +548,18 @@ void EventsExecutor::HandleAddedWaitable(py::handle waitable) // ended up there. We'll also have to hope that no Waitable implementations ever change their // component entities over their lifetimes. auto with_waitable = std::make_shared(waitable); - const py::object num_entities = waitable.attr("get_num_entities")(); - if (py::cast(num_entities.attr("num_guard_conditions")) != 0) { + const nb::object num_entities = waitable.attr("get_num_entities")(); + if (nb::cast(num_entities.attr("num_guard_conditions")) != 0) { throw std::runtime_error("Guard conditions not supported"); } auto wait_set = std::make_shared( - py::cast(num_entities.attr("num_subscriptions")), 0U, - py::cast(num_entities.attr("num_timers")), - py::cast(num_entities.attr("num_clients")), - py::cast(num_entities.attr("num_services")), - py::cast(num_entities.attr("num_events")), - py::cast(rclpy_context_.attr("handle"))); - auto with_waitset = std::make_shared(py::cast(wait_set)); + nb::cast(num_entities.attr("num_subscriptions")), 0U, + nb::cast(num_entities.attr("num_timers")), + nb::cast(num_entities.attr("num_clients")), + nb::cast(num_entities.attr("num_services")), + nb::cast(num_entities.attr("num_events")), + nb::cast(rclpy_context_.attr("handle"))); + auto with_waitset = std::make_shared(nb::cast(wait_set)); waitable.attr("add_to_wait_set")(wait_set); rcl_wait_set_t * const rcl_waitset = wait_set->rcl_ptr(); // We null out each entry in the waitset as we set it up, so that the waitset itself can be @@ -659,7 +655,7 @@ void EventsExecutor::HandleAddedWaitable(py::handle waitable) waitable_entities_[waitable] = std::move(sub_entities); } -void EventsExecutor::HandleRemovedWaitable(py::handle waitable) +void EventsExecutor::HandleRemovedWaitable(nb::handle waitable) { const auto nh = waitable_entities_.extract(waitable); if (!nh) { @@ -707,10 +703,10 @@ void EventsExecutor::HandleRemovedWaitable(py::handle waitable) } void EventsExecutor::HandleWaitableSubReady( - py::handle waitable, const rcl_subscription_t * rcl_sub, std::shared_ptr wait_set, + nb::handle waitable, const rcl_subscription_t * rcl_sub, std::shared_ptr wait_set, size_t wait_set_sub_index, std::shared_ptr, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // We need to set up the wait set to make it look like our subscription object is ready, and then // poke the Waitable to do what it needs to do from there. @@ -722,10 +718,10 @@ void EventsExecutor::HandleWaitableSubReady( } void EventsExecutor::HandleWaitableTimerReady( - py::handle waitable, const rcl_timer_t * rcl_timer, std::shared_ptr wait_set, + nb::handle waitable, const rcl_timer_t * rcl_timer, std::shared_ptr wait_set, size_t wait_set_timer_index, std::shared_ptr, std::shared_ptr) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // We need to set up the wait set to make it look like our timer object is ready, and then poke // the Waitable to do what it needs to do from there. @@ -737,10 +733,10 @@ void EventsExecutor::HandleWaitableTimerReady( } void EventsExecutor::HandleWaitableClientReady( - py::handle waitable, const rcl_client_t * rcl_client, std::shared_ptr wait_set, + nb::handle waitable, const rcl_client_t * rcl_client, std::shared_ptr wait_set, size_t wait_set_client_index, std::shared_ptr, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // We need to set up the wait set to make it look like our client object is ready, and then poke // the Waitable to do what it needs to do from there. @@ -752,10 +748,10 @@ void EventsExecutor::HandleWaitableClientReady( } void EventsExecutor::HandleWaitableServiceReady( - py::handle waitable, const rcl_service_t * rcl_service, std::shared_ptr wait_set, + nb::handle waitable, const rcl_service_t * rcl_service, std::shared_ptr wait_set, size_t wait_set_service_index, std::shared_ptr, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // We need to set up the wait set to make it look like our service object is ready, and then poke // the Waitable to do what it needs to do from there. @@ -767,10 +763,10 @@ void EventsExecutor::HandleWaitableServiceReady( } void EventsExecutor::HandleWaitableEventReady( - py::handle waitable, const rcl_event_t * rcl_event, std::shared_ptr wait_set, + nb::handle waitable, const rcl_event_t * rcl_event, std::shared_ptr wait_set, size_t wait_set_event_index, std::shared_ptr, size_t number_of_events) { - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // We need to set up the wait set to make it look like our event object is ready, and then poke // the Waitable to do what it needs to do from there. @@ -782,19 +778,19 @@ void EventsExecutor::HandleWaitableEventReady( } void EventsExecutor::HandleWaitableReady( - py::handle waitable, std::shared_ptr wait_set, size_t number_of_events) + nb::handle waitable, std::shared_ptr wait_set, size_t number_of_events) { if (stop_after_user_callback_) { events_queue_.Stop(); } // Largely based on rclpy.Executor._take_waitable() // https://github.com/ros2/rclpy/blob/a19180c238d4d97ed2b58868d8fb7fa3e3b621f2/rclpy/rclpy/executors.py#L447-L454 - py::object is_ready = waitable.attr("is_ready"); - py::object take_data = waitable.attr("take_data"); - py::object execute = waitable.attr("execute"); - py::object futures = waitable.attr("_futures"); - for (auto & future : futures) { - future.attr("_set_executor")(py::cast(this)); + nb::object is_ready = waitable.attr("is_ready"); + nb::object take_data = waitable.attr("take_data"); + nb::object execute = waitable.attr("execute"); + nb::object futures = waitable.attr("_futures"); + for (nb::handle future : futures) { + future.attr("_set_executor")(nb::cast(this)); } for (size_t i = 0; i < number_of_events; ++i) { // This method can have side effects, so it needs to be called even though it looks like just @@ -802,35 +798,35 @@ void EventsExecutor::HandleWaitableReady( if (!is_ready(wait_set)) { throw std::runtime_error("Failed to make Waitable ready"); } - py::object data = take_data(); + nb::object data = take_data(); // execute() is an async method, we need a Task to run it create_task(execute(data)); } } -void EventsExecutor::IterateTask(py::handle task) +void EventsExecutor::IterateTask(nb::handle task) { if (stop_after_user_callback_) { events_queue_.Stop(); } - py::gil_scoped_acquire gil_acquire; + nb::gil_scoped_acquire gil_acquire; // Calling this won't throw, but it may set the exception property on the task object. task(); - if (py::cast(task.attr("done")())) { - py::object ex = task.attr("exception")(); + if (nb::cast(task.attr("done")())) { + nb::object ex = task.attr("exception")(); // Drop reference with GIL held. This doesn't necessarily destroy the underlying Task, since // the `create_task()` caller may have retained a reference to the returned value. task.dec_ref(); if (!ex.is_none()) { // It's not clear how to easily turn a Python exception into a C++ one, so let's just throw - // it again and let pybind translate it normally. + // it again and let nanobind translate it normally. try { Raise(ex); - } catch (py::error_already_set & cpp_ex) { + } catch (nb::python_error & cpp_ex) { // There's no good way to know what node this task came from. If we only have one node, we // can use the logger from that, otherwise we'll have to leave it undefined. - py::object logger = py::none(); + nb::object logger = nb::none(); if (nodes_.size() == 1) { logger = nodes_[0].attr("get_logger")(); } @@ -842,25 +838,25 @@ void EventsExecutor::IterateTask(py::handle task) } void EventsExecutor::HandleCallbackExceptionInNodeEntity( - const py::error_already_set & exc, py::handle entity, const std::string & node_entity_attr) + const nb::python_error & exc, nb::handle entity, const std::string & node_entity_attr) { // Try to identify the node associated with the entity that threw the exception, so we can log to // it. - for (py::handle node : nodes_) { - if (py::set(node.attr(node_entity_attr.c_str())).contains(entity)) { + for (nb::handle node : nodes_) { + if (nb::set(nb::object(node.attr(node_entity_attr.c_str()))).contains(entity)) { return HandleCallbackExceptionWithLogger(exc, node.attr("get_logger")(), node_entity_attr); } } // Failed to find a node - HandleCallbackExceptionWithLogger(exc, py::none(), node_entity_attr); + HandleCallbackExceptionWithLogger(exc, nb::none(), node_entity_attr); } void EventsExecutor::HandleCallbackExceptionWithLogger( - const py::error_already_set & exc, py::object logger, const std::string & entity_type) + const nb::python_error & exc, nb::object logger, const std::string & entity_type) { if (logger.is_none()) { - py::object logging = py::module_::import("rclpy.logging"); + nb::object logging = nb::module_::import_("rclpy.logging"); logger = logging.attr("get_logger")("UNKNOWN"); } @@ -868,40 +864,55 @@ void EventsExecutor::HandleCallbackExceptionWithLogger( // same code location. Since it has no visibility into C++, all calls made from here will be // attributed to the python that last called into here. Instead we will call out to python for // logging. - py::dict scope; + nb::dict scope; scope["logger"] = logger; scope["node_entity_attr"] = entity_type; scope["exc_value"] = exc.value(); - scope["exc_trace"] = exc.trace(); - py::exec( - R"( + scope["exc_trace"] = exc.traceback(); + nb::exec( + nb::str( + R"( import traceback logger.fatal(f"Exception in '{node_entity_attr}' callback: {exc_value}") logger.warning("Error occurred at:\n" + "".join(traceback.format_tb(exc_trace))) -)", +)"), scope); } -void EventsExecutor::Raise(py::object ex) +void EventsExecutor::Raise(nb::object ex) { - py::dict scope; + nb::dict scope; scope["ex"] = ex; - py::exec("raise ex", scope); + nb::exec(nb::str("raise ex"), scope); } -// pybind11 module bindings +// nanobind module bindings -void define_events_executor(py::object module) +void define_events_executor(nb::object module) { - py::class_(module, "EventsExecutor") - .def(py::init(), py::arg("context")) - .def_property_readonly("context", &EventsExecutor::get_context) - .def("create_task", &EventsExecutor::create_task, py::arg("callback")) - .def("_call_task_in_next_spin", &EventsExecutor::call_task_in_next_spin, py::arg("task")) + // rclpy Futures hold weak references to their executor + nb::class_ cls(module, "EventsExecutor", nb::is_weak_referenceable()); + cls + .def( + "__init__", + [](nb::handle_t self, nb::object context) { + new (nb::inst_ptr(self)) EventsExecutor(context); + nb::inst_mark_ready(self); + // Wake the executor on context shutdown, like the pure Python executors do. + // Registering the pure Python trampoline (see below) bound to this instance + // makes Context.on_shutdown() see a real bound method, so it only keeps a + // weak reference and the registration doesn't need to be torn down at + // destruction time. + context.attr("on_shutdown")(self.attr("_on_shutdown_wake")); + }, + "context"_a) + .def_prop_ro("context", &EventsExecutor::get_context) + .def("create_task", &EventsExecutor::create_task, "callback"_a, "args"_a, "kwargs"_a) + .def("_call_task_in_next_spin", &EventsExecutor::call_task_in_next_spin, "task"_a) .def("create_future", &EventsExecutor::create_future) - .def("shutdown", &EventsExecutor::shutdown, py::arg("timeout_sec") = py::none()) - .def("add_node", &EventsExecutor::add_node, py::arg("node")) - .def("remove_node", &EventsExecutor::remove_node, py::arg("node")) + .def("shutdown", &EventsExecutor::shutdown, "timeout_sec"_a = nb::none()) + .def("add_node", &EventsExecutor::add_node, "node"_a) + .def("remove_node", &EventsExecutor::remove_node, "node"_a) .def("wake", &EventsExecutor::wake) .def("get_nodes", &EventsExecutor::get_nodes) .def("spin", [](EventsExecutor & exec) {exec.spin();}) @@ -910,21 +921,29 @@ void define_events_executor(py::object module) [](EventsExecutor & exec, std::optional timeout_sec) { exec.spin(timeout_sec, true); }, - py::arg("timeout_sec") = py::none()) + "timeout_sec"_a = nb::none()) .def( "spin_until_future_complete", - [](EventsExecutor & exec, py::handle future, std::optional timeout_sec) { + [](EventsExecutor & exec, nb::handle future, std::optional timeout_sec) { exec.spin_until_future_complete(future, timeout_sec); }, - py::arg("future"), py::arg("timeout_sec") = py::none()) + "future"_a, "timeout_sec"_a = nb::none()) .def( "spin_once_until_future_complete", - [](EventsExecutor & exec, py::handle future, std::optional timeout_sec) { + [](EventsExecutor & exec, nb::handle future, std::optional timeout_sec) { exec.spin_until_future_complete(future, timeout_sec, true); }, - py::arg("future"), py::arg("timeout_sec") = py::none()) + "future"_a, "timeout_sec"_a = nb::none()) .def("__enter__", &EventsExecutor::enter) - .def("__exit__", &EventsExecutor::exit); + .def( + "__exit__", &EventsExecutor::exit, nb::arg().none(), nb::arg().none(), nb::arg().none()); + + // Context.on_shutdown() only applies weak reference semantics to real Python bound + // methods (types.MethodType), which nanobind's methods are not. A plain Python + // trampoline function stored on the class binds like any Python method when accessed + // through an instance, and its __func__ stays alive via the class, so + // weakref.WeakMethod works with it. + cls.attr("_on_shutdown_wake") = nb::eval(nb::str("lambda self: self.wake()"), nb::dict()); } } // namespace events_executor diff --git a/rclpy/src/rclpy/events_executor/events_executor.hpp b/rclpy/src/rclpy/events_executor/events_executor.hpp index e395be9a0..c8a8bae23 100644 --- a/rclpy/src/rclpy/events_executor/events_executor.hpp +++ b/rclpy/src/rclpy/events_executor/events_executor.hpp @@ -16,7 +16,12 @@ #ifndef RCLPY__EVENTS_EXECUTOR__EVENTS_EXECUTOR_HPP_ #define RCLPY__EVENTS_EXECUTOR__EVENTS_EXECUTOR_HPP_ -#include +#include +#include +#include +#include +#include +#include #include #include @@ -41,6 +46,8 @@ #include "signal_handler.hpp" #include "wait_set.hpp" +namespace nb = nanobind; + namespace rclpy { namespace events_executor @@ -59,27 +66,27 @@ class EventsExecutor { public: /// @param context the rclpy Context object to operate on - explicit EventsExecutor(pybind11::object context); + explicit EventsExecutor(nb::object context); ~EventsExecutor(); // rclpy Executor API methods: - pybind11::object get_context() const {return rclpy_context_;} - pybind11::object create_task( - pybind11::object callback, pybind11::args args = {}, const pybind11::kwargs & kwargs = {}); - void call_task_in_next_spin(pybind11::handle task); - pybind11::object create_future(); + nb::object get_context() const {return rclpy_context_;} + nb::object create_task( + nb::object callback, nb::args args = {}, const nb::kwargs & kwargs = {}); + void call_task_in_next_spin(nb::handle task); + nb::object create_future(); bool shutdown(std::optional timeout_sec = {}); - bool add_node(pybind11::object node); - void remove_node(pybind11::handle node); + bool add_node(nb::object node); + void remove_node(nb::handle node); void wake(); - pybind11::list get_nodes() const; + nb::list get_nodes() const; void spin(std::optional timeout_sec = {}, bool stop_after_user_callback = false); void spin_until_future_complete( - pybind11::handle future, std::optional timeout_sec = {}, + nb::handle future, std::optional timeout_sec = {}, bool stop_after_user_callback = false); EventsExecutor * enter(); - void exit(pybind11::object, pybind11::object, pybind11::object); + void exit(nb::object, nb::object, nb::object); private: // Structure to hold entities discovered underlying a Waitable object. @@ -101,77 +108,77 @@ class EventsExecutor /// Given an existing set of entities and a set with the desired new state, updates the existing /// set and invokes callbacks on each added or removed entity. void UpdateEntitySet( - pybind11::set & entity_set, const pybind11::set & new_entity_set, - std::function added_entity_callback, - std::function removed_entity_callback); + nb::set & entity_set, const nb::set & new_entity_set, + std::function added_entity_callback, + std::function removed_entity_callback); - void HandleAddedSubscription(pybind11::handle); - void HandleRemovedSubscription(pybind11::handle); - void HandleSubscriptionReady(pybind11::handle, size_t number_of_events); + void HandleAddedSubscription(nb::handle); + void HandleRemovedSubscription(nb::handle); + void HandleSubscriptionReady(nb::handle, size_t number_of_events); - void HandleAddedTimer(pybind11::handle); - void HandleRemovedTimer(pybind11::handle); - void HandleTimerReady(pybind11::handle, const rcl_timer_call_info_t &); + void HandleAddedTimer(nb::handle); + void HandleRemovedTimer(nb::handle); + void HandleTimerReady(nb::handle, const rcl_timer_call_info_t &); - void HandleAddedClient(pybind11::handle); - void HandleRemovedClient(pybind11::handle); - void HandleClientReady(pybind11::handle, size_t number_of_events); + void HandleAddedClient(nb::handle); + void HandleRemovedClient(nb::handle); + void HandleClientReady(nb::handle, size_t number_of_events); - void HandleAddedService(pybind11::handle); - void HandleRemovedService(pybind11::handle); - void HandleServiceReady(pybind11::handle, size_t number_of_events); + void HandleAddedService(nb::handle); + void HandleRemovedService(nb::handle); + void HandleServiceReady(nb::handle, size_t number_of_events); - void HandleAddedWaitable(pybind11::handle); - void HandleRemovedWaitable(pybind11::handle); + void HandleAddedWaitable(nb::handle); + void HandleRemovedWaitable(nb::handle); void HandleWaitableSubReady( - pybind11::handle waitable, const rcl_subscription_t *, + nb::handle waitable, const rcl_subscription_t *, std::shared_ptr wait_set, size_t wait_set_sub_index, std::shared_ptr with_waitset, size_t number_of_events); void HandleWaitableTimerReady( - pybind11::handle waitable, const rcl_timer_t *, std::shared_ptr wait_set, + nb::handle waitable, const rcl_timer_t *, std::shared_ptr wait_set, size_t wait_set_timer_index, std::shared_ptr with_waitable, std::shared_ptr with_waitset); void HandleWaitableClientReady( - pybind11::handle waitable, const rcl_client_t *, std::shared_ptr wait_set, + nb::handle waitable, const rcl_client_t *, std::shared_ptr wait_set, size_t wait_set_client_index, std::shared_ptr with_waitset, size_t number_of_events); void HandleWaitableServiceReady( - pybind11::handle waitable, const rcl_service_t *, std::shared_ptr wait_set, + nb::handle waitable, const rcl_service_t *, std::shared_ptr wait_set, size_t wait_set_service_index, std::shared_ptr with_waitset, size_t number_of_events); void HandleWaitableEventReady( - pybind11::handle waitable, const rcl_event_t *, std::shared_ptr wait_set, + nb::handle waitable, const rcl_event_t *, std::shared_ptr wait_set, size_t wait_set_event_index, std::shared_ptr with_waitset, size_t number_of_events); void HandleWaitableReady( - pybind11::handle waitable, std::shared_ptr wait_set, size_t number_of_events); + nb::handle waitable, std::shared_ptr wait_set, size_t number_of_events); /// Helper for create_task(). @p task needs to have had one reference manually added to it. See /// create_task() implementation for details. - void IterateTask(pybind11::handle task); + void IterateTask(nb::handle task); void HandleCallbackExceptionInNodeEntity( - const pybind11::error_already_set &, pybind11::handle entity, + const nb::python_error &, nb::handle entity, const std::string & node_entity_attr); void HandleCallbackExceptionWithLogger( - const pybind11::error_already_set &, pybind11::object logger, const std::string & entity_type); + const nb::python_error &, nb::object logger, const std::string & entity_type); /// Raises the given python object instance as a Python exception - void Raise(pybind11::object); + void Raise(nb::object); - const pybind11::object rclpy_context_; + const nb::object rclpy_context_; // Imported python objects we depend on - const pybind11::object inspect_iscoroutine_; - const pybind11::object inspect_signature_; - const pybind11::object rclpy_task_; - const pybind11::object rclpy_future_; - const pybind11::object rclpy_timer_timer_info_; + const nb::object inspect_iscoroutine_; + const nb::object inspect_signature_; + const nb::object rclpy_task_; + const nb::object rclpy_future_; + const nb::object rclpy_timer_timer_info_; EventsQueue events_queue_; ScopedSignalCallback signal_callback_; - pybind11::set nodes_; ///< The set of all nodes we're executing + nb::set nodes_; ///< The set of all nodes we're executing std::atomic wake_pending_{}; ///< An unhandled call to wake() has been made std::timed_mutex spinning_mutex_; ///< Held while a thread is spinning @@ -180,22 +187,22 @@ class EventsExecutor bool stop_after_user_callback_{}; // Collection of awaitable entities we're servicing - pybind11::set subscriptions_; - pybind11::set timers_; - pybind11::set clients_; - pybind11::set services_; - pybind11::set waitables_; + nb::set subscriptions_; + nb::set timers_; + nb::set clients_; + nb::set services_; + nb::set waitables_; /// Cache for rcl pointers underlying each waitables_ entry, because those are harder to retrieve /// than the other entity types. - std::unordered_map waitable_entities_; RclCallbackManager rcl_callback_manager_; TimersManager timers_manager_; }; -void define_events_executor(pybind11::object module); +void define_events_executor(nb::object module); } // namespace events_executor } // namespace rclpy diff --git a/rclpy/src/rclpy/events_executor/python_eq_handler.hpp b/rclpy/src/rclpy/events_executor/python_eq_handler.hpp index 3a3f3b1b9..2148ad9c0 100644 --- a/rclpy/src/rclpy/events_executor/python_eq_handler.hpp +++ b/rclpy/src/rclpy/events_executor/python_eq_handler.hpp @@ -15,18 +15,21 @@ #ifndef RCLPY__EVENTS_EXECUTOR__PYTHON_EQ_HANDLER_HPP_ #define RCLPY__EVENTS_EXECUTOR__PYTHON_EQ_HANDLER_HPP_ -#include +#include + +namespace nb = nanobind; namespace rclpy { namespace events_executor { -/// This is a workaround to the deprecation of `operator==` in Pybind11 >=2.2 -/// See https://pybind11.readthedocs.io/en/stable/upgrade.html#deprecation-of-some-py-object-apis -/// It's intended to be replaced with the +/// This is intended to be used as the KeyEqual template arg to STL containers using a +/// nb::handle as a Key, comparing keys by Python object identity. nanobind defines no +/// `operator==` for handles (callers must pick identity or equality explicitly via +/// `.is()` or `.equal()`), so the default std::equal_to would not compile. struct PythonEqHandler { - inline auto operator()(const pybind11::handle & x, const pybind11::handle & y) const + inline auto operator()(const nb::handle & x, const nb::handle & y) const { return x.is(y); } diff --git a/rclpy/src/rclpy/events_executor/python_hasher.hpp b/rclpy/src/rclpy/events_executor/python_hasher.hpp index c765645ac..2492f5420 100644 --- a/rclpy/src/rclpy/events_executor/python_hasher.hpp +++ b/rclpy/src/rclpy/events_executor/python_hasher.hpp @@ -16,20 +16,26 @@ #ifndef RCLPY__EVENTS_EXECUTOR__PYTHON_HASHER_HPP_ #define RCLPY__EVENTS_EXECUTOR__PYTHON_HASHER_HPP_ -#include +#include + +namespace nb = nanobind; namespace rclpy { namespace events_executor { /// This is intended to be used as the Hash template arg to STL containers using a -/// pybind11::handle as a Key. This is the same hash that a native Python dict or set +/// nb::handle as a Key. This is the same hash that a native Python dict or set /// would use given the same key. struct PythonHasher { - inline auto operator()(const pybind11::handle & handle) const + inline auto operator()(const nb::handle & handle) const { - return pybind11::hash(handle); + Py_hash_t hash = PyObject_Hash(handle.ptr()); + if (-1 == hash) { + throw nb::python_error(); + } + return static_cast(hash); } }; } // namespace events_executor diff --git a/rclpy/src/rclpy/events_executor/rcl_support.cpp b/rclpy/src/rclpy/events_executor/rcl_support.cpp index 3a8e54c68..246f3c9f9 100644 --- a/rclpy/src/rclpy/events_executor/rcl_support.cpp +++ b/rclpy/src/rclpy/events_executor/rcl_support.cpp @@ -16,7 +16,7 @@ #include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -43,8 +43,8 @@ RclCallbackManager::~RclCallbackManager() // Should not still have any callbacks registered when we exit, because otherwise RCL can call // pointers that will no longer be valid. We can't throw an exception here, but we can explode. if (!owned_cbs_.empty()) { - py::gil_scoped_acquire gil_acquire; - py::print("Destroying callback manager with callbacks remaining"); + nb::gil_scoped_acquire gil_acquire; + nb::print("Destroying callback manager with callbacks remaining"); ::abort(); } } @@ -55,7 +55,7 @@ const void * RclCallbackManager::MakeCallback( // We don't support replacing an existing callback with a new one, because it gets tricky making // sure we don't delete an old callback while the middleware still holds a pointer to it. if (owned_cbs_.count(key) != 0) { - throw py::key_error("Attempt to replace existing callback"); + throw nb::key_error("Attempt to replace existing callback"); } CbEntry new_entry; new_entry.cb = @@ -78,7 +78,7 @@ const void * RclCallbackManager::MakeCallback( void RclCallbackManager::RemoveCallback(const void * key) { if (!owned_cbs_.erase(key)) { - throw py::key_error("Attempt to remove nonexistent callback"); + throw nb::key_error("Attempt to remove nonexistent callback"); } } diff --git a/rclpy/src/rclpy/events_executor/scoped_with.hpp b/rclpy/src/rclpy/events_executor/scoped_with.hpp index 49f776adf..bb84e6cce 100644 --- a/rclpy/src/rclpy/events_executor/scoped_with.hpp +++ b/rclpy/src/rclpy/events_executor/scoped_with.hpp @@ -16,7 +16,9 @@ #ifndef RCLPY__EVENTS_EXECUTOR__SCOPED_WITH_HPP_ #define RCLPY__EVENTS_EXECUTOR__SCOPED_WITH_HPP_ -#include +#include + +namespace nb = nanobind; namespace rclpy { @@ -27,16 +29,16 @@ namespace events_executor class ScopedWith { public: - explicit ScopedWith(pybind11::handle object) - : object_(pybind11::cast(object)) + explicit ScopedWith(nb::handle object) + : object_(nb::cast(object)) { object_.attr("__enter__")(); } - ~ScopedWith() {object_.attr("__exit__")(pybind11::none(), pybind11::none(), pybind11::none());} + ~ScopedWith() {object_.attr("__exit__")(nb::none(), nb::none(), nb::none());} private: - pybind11::object object_; + nb::object object_; }; } // namespace events_executor diff --git a/rclpy/src/rclpy/events_executor/timers_manager.cpp b/rclpy/src/rclpy/events_executor/timers_manager.cpp index f21c951c3..39a5c4c5e 100644 --- a/rclpy/src/rclpy/events_executor/timers_manager.cpp +++ b/rclpy/src/rclpy/events_executor/timers_manager.cpp @@ -28,7 +28,7 @@ #include "timer.hpp" namespace pl = std::placeholders; -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -129,9 +129,10 @@ class RclTimersManager::ClockManager : public std::enable_shared_from_thisfirst); @@ -152,8 +153,8 @@ class RclTimersManager::ClockManager : public std::enable_shared_from_this= WARN_TIMERS_COUNT) { - py::print("Warning, the number of timers associated with this clock is large."); - py::print("Management of this number of timers may be inefficient."); + nb::print("Warning, the number of timers associated with this clock is large."); + nb::print("Management of this number of timers may be inefficient."); } UpdateTimers(); } @@ -162,7 +163,7 @@ class RclTimersManager::ClockManager : public std::enable_shared_from_thissecond->RemoveTimer(timer); if (it->second->empty()) { @@ -344,28 +345,28 @@ void RclTimersManager::RemoveTimer(rcl_timer_t * timer) TimersManager::TimersManager( EventsQueue * events_queue, - std::function timer_ready_callback) + std::function timer_ready_callback) : rcl_manager_(events_queue), ready_callback_(timer_ready_callback) { } TimersManager::~TimersManager() {} -void TimersManager::AddTimer(py::handle timer) +void TimersManager::AddTimer(nb::handle timer) { PyRclMapping mapping; - py::handle handle = timer.attr("handle"); + nb::handle handle = timer.attr("handle"); mapping.with = std::make_unique(handle); - mapping.rcl_ptr = py::cast(handle).rcl_ptr(); + mapping.rcl_ptr = nb::cast(handle).rcl_ptr(); rcl_manager_.AddTimer(mapping.rcl_ptr, std::bind(ready_callback_, timer, pl::_1)); timer_mappings_[timer] = std::move(mapping); } -void TimersManager::RemoveTimer(py::handle timer) +void TimersManager::RemoveTimer(nb::handle timer) { const auto it = timer_mappings_.find(timer); if (it == timer_mappings_.end()) { - throw py::key_error("Attempt to remove unmanaged timer"); + throw nb::key_error("Attempt to remove unmanaged timer"); } rcl_manager_.RemoveTimer(it->second.rcl_ptr); timer_mappings_.erase(it); diff --git a/rclpy/src/rclpy/events_executor/timers_manager.hpp b/rclpy/src/rclpy/events_executor/timers_manager.hpp index 17d19051d..534210070 100644 --- a/rclpy/src/rclpy/events_executor/timers_manager.hpp +++ b/rclpy/src/rclpy/events_executor/timers_manager.hpp @@ -16,7 +16,9 @@ #ifndef RCLPY__EVENTS_EXECUTOR__TIMERS_MANAGER_HPP_ #define RCLPY__EVENTS_EXECUTOR__TIMERS_MANAGER_HPP_ -#include +#include +#include +#include #include #include @@ -30,6 +32,8 @@ #include "events_executor/python_hasher.hpp" #include "events_executor/scoped_with.hpp" +namespace nb = nanobind; + namespace rclpy { namespace events_executor @@ -63,15 +67,15 @@ class TimersManager /// timer is ready for servicing. TimersManager( EventsQueue * events_queue, - std::function timer_ready_callback); + std::function timer_ready_callback); ~TimersManager(); /// Accessor for underlying rcl timer manager, for management of non-Python timers. RclTimersManager & rcl_manager() {return rcl_manager_;} // Both of these methods expect the GIL to be held when they are called. - void AddTimer(pybind11::handle timer); - void RemoveTimer(pybind11::handle timer); + void AddTimer(nb::handle timer); + void RemoveTimer(nb::handle timer); private: struct PyRclMapping @@ -85,9 +89,9 @@ class TimersManager }; RclTimersManager rcl_manager_; - const std::function ready_callback_; + const std::function ready_callback_; - std::unordered_map timer_mappings_; + std::unordered_map timer_mappings_; }; } // namespace events_executor diff --git a/rclpy/src/rclpy/graph.cpp b/rclpy/src/rclpy/graph.cpp index 7329659ad..604d2012c 100644 --- a/rclpy/src/rclpy/graph.cpp +++ b/rclpy/src/rclpy/graph.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -32,7 +33,7 @@ namespace rclpy { -py::list +nb::list graph_get_publisher_names_and_types_by_node( Node & node, bool no_demangle, std::string node_name, std::string node_namespace) @@ -65,7 +66,7 @@ graph_get_publisher_names_and_types_by_node( return convert_to_py_names_and_types(&publisher_names_and_types); } -py::list +nb::list graph_get_subscriber_names_and_types_by_node( Node & node, bool no_demangle, std::string node_name, std::string node_namespace) @@ -98,7 +99,7 @@ graph_get_subscriber_names_and_types_by_node( return convert_to_py_names_and_types(&subscriber_names_and_types); } -py::list +nb::list graph_get_service_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace) { @@ -130,7 +131,7 @@ graph_get_service_names_and_types_by_node( return convert_to_py_names_and_types(&service_names_and_types); } -py::list +nb::list graph_get_client_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace) { @@ -161,7 +162,7 @@ graph_get_client_names_and_types_by_node( return convert_to_py_names_and_types(&client_names_and_types); } -py::list +nb::list graph_get_topic_names_and_types(Node & node, bool no_demangle) { rcl_names_and_types_t topic_names_and_types = rcl_get_zero_initialized_names_and_types(); @@ -188,7 +189,7 @@ graph_get_topic_names_and_types(Node & node, bool no_demangle) return convert_to_py_names_and_types(&topic_names_and_types); } -py::list +nb::list graph_get_service_names_and_types(Node & node) { rcl_names_and_types_t service_names_and_types = rcl_get_zero_initialized_names_and_types(); @@ -221,7 +222,7 @@ typedef rcl_ret_t (* rcl_get_info_by_topic_func_t)( bool no_mangle, rcl_topic_endpoint_info_array_t * info_array); -py::list +nb::list _get_info_by_topic( Node & node, const char * topic_name, @@ -260,7 +261,7 @@ _get_info_by_topic( return convert_to_py_topic_endpoint_info_list(&info_array); } -py::list +nb::list graph_get_publishers_info_by_topic( Node & node, const char * topic_name, bool no_mangle) { @@ -269,7 +270,7 @@ graph_get_publishers_info_by_topic( rcl_get_publishers_info_by_topic); } -py::list +nb::list graph_get_subscriptions_info_by_topic( Node & node, const char * topic_name, bool no_mangle) { @@ -286,7 +287,7 @@ typedef rcl_ret_t (* rcl_get_info_by_service_func_t)( rcl_service_endpoint_info_array_t * info_array); -py::list +nb::list _get_info_by_service( Node & node, const char * service_name, @@ -326,7 +327,7 @@ _get_info_by_service( return convert_to_py_service_endpoint_info_list(&info_array); } -py::list +nb::list graph_get_clients_info_by_service( Node & node, const char * service_name, bool no_mangle) { @@ -335,7 +336,7 @@ graph_get_clients_info_by_service( rcl_get_clients_info_by_service); } -py::list +nb::list graph_get_servers_info_by_service( Node & node, const char * service_name, bool no_mangle) { @@ -344,7 +345,7 @@ graph_get_servers_info_by_service( rcl_get_servers_info_by_service); } -py::list +nb::list graph_get_action_client_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace) { @@ -376,7 +377,7 @@ graph_get_action_client_names_and_types_by_node( return convert_to_py_names_and_types(&action_names_and_types); } -py::list +nb::list graph_get_action_server_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace) { @@ -408,7 +409,7 @@ graph_get_action_server_names_and_types_by_node( return convert_to_py_names_and_types(&action_names_and_types); } -py::list +nb::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(); @@ -441,7 +442,7 @@ typedef rcl_ret_t (* rcl_action_get_info_by_action_func_t)( rcl_action_endpoint_info_array_t * info_array); -py::list +nb::list _get_info_by_action( Node & node, const char * action_name, @@ -480,7 +481,7 @@ _get_info_by_action( return convert_to_py_action_endpoint_info_list(&info_array); } -py::list +nb::list graph_get_action_clients_info_by_action( Node & node, const char * action_name) { @@ -489,7 +490,7 @@ graph_get_action_clients_info_by_action( rcl_action_get_clients_info_by_action); } -py::list +nb::list graph_get_action_servers_info_by_action( Node & node, const char * action_name) { diff --git a/rclpy/src/rclpy/graph.hpp b/rclpy/src/rclpy/graph.hpp index 792d30a4d..a8778a02b 100644 --- a/rclpy/src/rclpy/graph.hpp +++ b/rclpy/src/rclpy/graph.hpp @@ -15,13 +15,14 @@ #ifndef RCLPY__GRAPH_HPP_ #define RCLPY__GRAPH_HPP_ -#include +#include +#include #include #include "node.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -41,7 +42,7 @@ namespace rclpy * strings). * \see rcl_get_publisher_names_and_types_by_node */ -py::list +nb::list graph_get_publisher_names_and_types_by_node( Node & node, bool no_demangle, std::string node_name, std::string node_namespace); @@ -61,7 +62,7 @@ graph_get_publisher_names_and_types_by_node( * strings). * \see rcl_get_subscriber_names_and_types_by_node */ -py::list +nb::list graph_get_subscriber_names_and_types_by_node( Node & node, bool no_demangle, std::string node_name, std::string node_namespace); @@ -79,7 +80,7 @@ graph_get_subscriber_names_and_types_by_node( * strings). * \see rcl_get_service_names_and_types_by_node */ -py::list +nb::list graph_get_service_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace); @@ -96,7 +97,7 @@ graph_get_service_names_and_types_by_node( * strings). * \see rcl_get_client_names_and_types_by_node */ -py::list +nb::list graph_get_client_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace); @@ -112,7 +113,7 @@ graph_get_client_names_and_types_by_node( * strings). * \see rcl_get_topic_names_and_types */ -py::list +nb::list graph_get_topic_names_and_types(Node & node, bool no_demangle); /// Get all service names and types in the ROS graph. @@ -125,7 +126,7 @@ graph_get_topic_names_and_types(Node & node, bool no_demangle); * strings). * \see rcl_get_service_names_and_types */ -py::list +nb::list graph_get_service_names_and_types(Node & node); /// Return a list of publishers on a given topic. @@ -142,7 +143,7 @@ graph_get_service_names_and_types(Node & node); * otherwise it should be a valid ROS topic name. * \return list of publishers */ -py::list +nb::list graph_get_publishers_info_by_topic( Node & node, const char * topic_name, bool no_mangle); @@ -160,7 +161,7 @@ graph_get_publishers_info_by_topic( * otherwise it should be a valid ROS topic name. * \return list of subscriptions. */ -py::list +nb::list graph_get_subscriptions_info_by_topic( Node & node, const char * topic_name, bool no_mangle); @@ -178,7 +179,7 @@ graph_get_subscriptions_info_by_topic( * otherwise it should be a valid ROS service name. * \return list of clients. */ -py::list +nb::list graph_get_clients_info_by_service( Node & node, const char * service_name, bool no_mangle); @@ -196,7 +197,7 @@ graph_get_clients_info_by_service( * otherwise it should be a valid ROS service name. * \return list of servers. */ -py::list +nb::list graph_get_servers_info_by_service( Node & node, const char * service_name, bool no_mangle); @@ -213,7 +214,7 @@ graph_get_servers_info_by_service( * strings). * \see rcl_action_get_client_names_and_types_by_node */ -py::list +nb::list graph_get_action_client_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace); @@ -230,7 +231,7 @@ graph_get_action_client_names_and_types_by_node( * strings). * \see rcl_action_get_server_names_and_types_by_node */ -py::list +nb::list graph_get_action_server_names_and_types_by_node( Node & node, std::string node_name, std::string node_namespace); @@ -244,7 +245,7 @@ graph_get_action_server_names_and_types_by_node( * strings). * \see rcl_action_get_names_and_types */ -py::list +nb::list graph_get_action_names_and_types(Node & node); /// Return a list of action clients on a given action. @@ -262,7 +263,7 @@ graph_get_action_names_and_types(Node & node); * \return list of action clients. * \see rcl_action_get_clients_info_by_action */ -py::list +nb::list graph_get_action_clients_info_by_action( Node & node, const char * action_name); @@ -281,7 +282,7 @@ graph_get_action_clients_info_by_action( * \return list of action servers. * \see rcl_action_get_servers_info_by_action */ -py::list +nb::list graph_get_action_servers_info_by_action( Node & node, const char * action_name); diff --git a/rclpy/src/rclpy/guard_condition.cpp b/rclpy/src/rclpy/guard_condition.cpp index 7c39c9bfe..669d0ee99 100644 --- a/rclpy/src/rclpy/guard_condition.cpp +++ b/rclpy/src/rclpy/guard_condition.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -67,11 +68,11 @@ GuardCondition::trigger_guard_condition() } } -void define_guard_condition(py::object module) +void define_guard_condition(nb::object module) { - py::class_>(module, "GuardCondition") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "GuardCondition") + .def(nb::init()) + .def_prop_ro( "pointer", [](const GuardCondition & guard_condition) { return reinterpret_cast(guard_condition.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/guard_condition.hpp b/rclpy/src/rclpy/guard_condition.hpp index be434c46e..e4d2c6275 100644 --- a/rclpy/src/rclpy/guard_condition.hpp +++ b/rclpy/src/rclpy/guard_condition.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__GUARD_CONDITION_HPP_ #define RCLPY__GUARD_CONDITION_HPP_ -#include +#include +#include #include @@ -25,7 +26,7 @@ #include "destroyable.hpp" #include "utils.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -60,8 +61,8 @@ class GuardCondition : public Destroyable, public std::enable_shared_from_this rcl_guard_condition_; }; -/// Define a pybind11 wrapper for an rclpy::GuardCondition -void define_guard_condition(py::object module); +/// Define a nanobind wrapper for an rclpy::GuardCondition +void define_guard_condition(nb::object module); } // namespace rclpy #endif // RCLPY__GUARD_CONDITION_HPP_ diff --git a/rclpy/src/rclpy/lifecycle.cpp b/rclpy/src/rclpy/lifecycle.cpp index 8d7ed4805..08b5ce096 100644 --- a/rclpy/src/rclpy/lifecycle.cpp +++ b/rclpy/src/rclpy/lifecycle.cpp @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include +#include +#include +#include +#include #include #include @@ -41,7 +44,7 @@ #include "node.hpp" #include "service.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace { @@ -166,10 +169,10 @@ class LifecycleStateMachine : public rclpy::Destroyable, return std::string{}; } - py::tuple + nb::tuple get_current_state() { - return py::make_tuple( + return nb::make_tuple( state_machine_->current_state->id, state_machine_->current_state->label); } @@ -301,24 +304,24 @@ convert_callback_ret_code_to_label(TransitionCallbackReturnType cb_ret) namespace rclpy { void -define_lifecycle_api(py::module m) +define_lifecycle_api(nb::module_ m) { - py::class_>( + nb::class_( m, "LifecycleStateMachine") - .def(py::init()) - .def_property_readonly( + .def(nb::init()) + .def_prop_ro( "initialized", &LifecycleStateMachine::is_initialized, "Check if state machine is initialized.") - .def_property_readonly( + .def_prop_ro( "current_state", &LifecycleStateMachine::get_current_state, "Get the current state machine state.") - .def_property_readonly( + .def_prop_ro( "available_states", &LifecycleStateMachine::get_available_states, "Get the available states.") - .def_property_readonly( + .def_prop_ro( "available_transitions", &LifecycleStateMachine::get_available_transitions, "Get the available transitions.") - .def_property_readonly( + .def_prop_ro( "transition_graph", &LifecycleStateMachine::get_transition_graph, "Get the transition graph.") .def( @@ -330,22 +333,22 @@ define_lifecycle_api(py::module m) .def( "trigger_transition_by_label", &LifecycleStateMachine::trigger_transition_by_label, "Trigger a transition by label.") - .def_property_readonly( + .def_prop_ro( "service_change_state", &LifecycleStateMachine::get_srv_change_state, "Get the change state service.") - .def_property_readonly( + .def_prop_ro( "service_get_state", &LifecycleStateMachine::get_srv_get_state, "Get the get state service.") - .def_property_readonly( + .def_prop_ro( "service_get_available_states", &LifecycleStateMachine::get_srv_get_available_states, "Get the get available states service.") - .def_property_readonly( + .def_prop_ro( "service_get_available_transitions", &LifecycleStateMachine::get_srv_get_available_transitions, "Get the get available transitions service.") - .def_property_readonly( + .def_prop_ro( "service_get_transition_graph", &LifecycleStateMachine::get_srv_get_transition_graph, "Get the get transition graph service."); - py::enum_(m, "TransitionCallbackReturnType") + nb::enum_(m, "TransitionCallbackReturnType", nb::is_arithmetic()) .value( "SUCCESS", TransitionCallbackReturnType::Success, "Callback succeeded.") diff --git a/rclpy/src/rclpy/lifecycle.hpp b/rclpy/src/rclpy/lifecycle.hpp index 8c341521c..3ef4313c0 100644 --- a/rclpy/src/rclpy/lifecycle.hpp +++ b/rclpy/src/rclpy/lifecycle.hpp @@ -15,17 +15,17 @@ #ifndef RCLPY__LIFECYCLE_HPP_ #define RCLPY__LIFECYCLE_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { /// Define methods on a module for the lifecycle API /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_lifecycle_api(py::module module); +define_lifecycle_api(nb::module_ module); } // namespace rclpy #endif // RCLPY__LIFECYCLE_HPP_ diff --git a/rclpy/src/rclpy/logging.cpp b/rclpy/src/rclpy/logging.cpp index cd38c9851..e49828f6c 100644 --- a/rclpy/src/rclpy/logging.cpp +++ b/rclpy/src/rclpy/logging.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include diff --git a/rclpy/src/rclpy/logging.hpp b/rclpy/src/rclpy/logging.hpp index 1a58c7f02..1b5fd8377 100644 --- a/rclpy/src/rclpy/logging.hpp +++ b/rclpy/src/rclpy/logging.hpp @@ -15,13 +15,13 @@ #ifndef RCLPY__LOGGING_HPP_ #define RCLPY__LOGGING_HPP_ -#include +#include #include #include "context.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { diff --git a/rclpy/src/rclpy/logging_api.hpp b/rclpy/src/rclpy/logging_api.hpp index 68ffe3d24..0e8988365 100644 --- a/rclpy/src/rclpy/logging_api.hpp +++ b/rclpy/src/rclpy/logging_api.hpp @@ -15,17 +15,17 @@ #ifndef RCLPY__LOGGING_API_HPP_ #define RCLPY__LOGGING_API_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { /// Define methods on a module for the logging API /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_logging_api(py::module module); +define_logging_api(nb::module_ module); } // namespace rclpy #endif // RCLPY__LOGGING_API_HPP_ diff --git a/rclpy/src/rclpy/names.cpp b/rclpy/src/rclpy/names.cpp index c0ea46d8e..e6bf2df8d 100644 --- a/rclpy/src/rclpy/names.cpp +++ b/rclpy/src/rclpy/names.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -36,7 +37,7 @@ namespace rclpy { -py::object +nb::object get_validation_error_for_topic_name(const char * topic_name) { int validation_result; @@ -50,15 +51,15 @@ get_validation_error_for_topic_name(const char * topic_name) } if (RCL_TOPIC_NAME_VALID == validation_result) { - return py::none(); + return nb::none(); } const char * validation_message = rcl_topic_name_validation_result_string(validation_result); - return py::make_tuple(validation_message, invalid_index); + return nb::make_tuple(validation_message, invalid_index); } -py::object +nb::object get_validation_error_for_full_topic_name(const char * topic_name) { int validation_result; @@ -72,15 +73,15 @@ get_validation_error_for_full_topic_name(const char * topic_name) } if (validation_result == RMW_NAMESPACE_VALID) { - return py::none(); + return nb::none(); } const char * validation_message = rmw_full_topic_name_validation_result_string(validation_result); - return py::make_tuple(validation_message, invalid_index); + return nb::make_tuple(validation_message, invalid_index); } -py::object +nb::object get_validation_error_for_namespace(const char * namespace_) { int validation_result; @@ -94,15 +95,15 @@ get_validation_error_for_namespace(const char * namespace_) } if (RMW_NAMESPACE_VALID == validation_result) { - return py::none(); + return nb::none(); } const char * validation_message = rmw_namespace_validation_result_string(validation_result); - return py::make_tuple(validation_message, invalid_index); + return nb::make_tuple(validation_message, invalid_index); } -py::object +nb::object get_validation_error_for_node_name(const char * node_name) { int validation_result; @@ -116,12 +117,12 @@ get_validation_error_for_node_name(const char * node_name) } if (RMW_NODE_NAME_VALID == validation_result) { - return py::none(); + return nb::none(); } const char * validation_message = rmw_node_name_validation_result_string(validation_result); - return py::make_tuple(validation_message, invalid_index); + return nb::make_tuple(validation_message, invalid_index); } std::string @@ -186,17 +187,17 @@ expand_topic_name(const char * topic, const char * node_name, const char * node_ std::string error_text{"topic name '"}; error_text += topic; error_text += "' is invalid"; - throw py::value_error(append_rcl_error(error_text)); + throw nb::value_error(append_rcl_error(error_text).c_str()); } else if (RCL_RET_NODE_INVALID_NAME == ret) { std::string error_text{"node name '"}; error_text += node_name; error_text += "' is invalid"; - throw py::value_error(append_rcl_error(error_text)); + throw nb::value_error(append_rcl_error(error_text).c_str()); } else if (RCL_RET_NODE_INVALID_NAMESPACE == ret) { std::string error_text{"node namespace '"}; error_text += node_namespace; error_text += "' is invalid"; - throw py::value_error(append_rcl_error(error_text)); + throw nb::value_error(append_rcl_error(error_text).c_str()); } else if (RCL_RET_OK != ret) { throw RCLError("failed to do name expansion"); } diff --git a/rclpy/src/rclpy/names.hpp b/rclpy/src/rclpy/names.hpp index 20379f85a..8ceb893e9 100644 --- a/rclpy/src/rclpy/names.hpp +++ b/rclpy/src/rclpy/names.hpp @@ -15,13 +15,14 @@ #ifndef RCLPY__NAMES_HPP_ #define RCLPY__NAMES_HPP_ -#include +#include +#include #include #include "node.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -37,7 +38,7 @@ namespace rclpy * \return tuple of error message and invalid index if invalid, or * \return None if valid */ -py::object +nb::object get_validation_error_for_topic_name(const char * topic_name); /// Validate a full topic name and return error message and index of invalidation. @@ -51,7 +52,7 @@ get_validation_error_for_topic_name(const char * topic_name); * \return tuple of error message and invalid index if invalid, or * \return None if valid */ -py::object +nb::object get_validation_error_for_full_topic_name(const char * topic_name); /// Validate a namespace and return error message and index of invalidation. @@ -63,7 +64,7 @@ get_validation_error_for_full_topic_name(const char * topic_name); * \return tuple of error message and invalid index if invalid, or * \return None if valid */ -py::object +nb::object get_validation_error_for_namespace(const char * namespace_); /// Validate a node name and return error message and index of invalidation. @@ -75,7 +76,7 @@ get_validation_error_for_namespace(const char * namespace_); * \return tuple of error message and invalid index if invalid, or * \return None if valid */ -py::object +nb::object get_validation_error_for_node_name(const char * node_name); /// Expand a topic name diff --git a/rclpy/src/rclpy/node.cpp b/rclpy/src/rclpy/node.cpp index 6ce67cbc6..aa37f89c2 100644 --- a/rclpy/src/rclpy/node.cpp +++ b/rclpy/src/rclpy/node.cpp @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include +#include #include #include @@ -158,7 +161,7 @@ Node::get_count_action_servers(const char * action_name) return count; } -py::list +nb::list Node::get_names_impl(bool get_enclaves) { rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -212,30 +215,30 @@ Node::get_names_impl(bool get_enclaves) } }); - py::list pynode_names_and_namespaces(node_names.size); + nb::list pynode_names_and_namespaces = create_sized_list(node_names.size); for (size_t idx = 0; idx < node_names.size; ++idx) { if (get_enclaves) { - pynode_names_and_namespaces[idx] = py::make_tuple( - py::str(node_names.data[idx]), - py::str(node_namespaces.data[idx]), - py::str(enclaves.data[idx])); + pynode_names_and_namespaces[idx] = nb::make_tuple( + nb::str(node_names.data[idx]), + nb::str(node_namespaces.data[idx]), + nb::str(enclaves.data[idx])); } else { - pynode_names_and_namespaces[idx] = py::make_tuple( - py::str(node_names.data[idx]), - py::str(node_namespaces.data[idx])); + pynode_names_and_namespaces[idx] = nb::make_tuple( + nb::str(node_names.data[idx]), + nb::str(node_namespaces.data[idx])); } } return pynode_names_and_namespaces; } -py::list +nb::list Node::get_node_names_and_namespaces() { return get_names_impl(false); } -py::list +nb::list Node::get_node_names_and_namespaces_with_enclaves() { return get_names_impl(true); @@ -249,61 +252,61 @@ Node::get_node_names_and_namespaces_with_enclaves() * \param[in] pyparameter_type_cls the Parameter.Type class * \return an instance of pyparameter_cls */ -py::object +nb::object _parameter_from_rcl_variant( - py::str pyname, rcl_variant_t * variant, py::object pyparameter_cls, - py::object pyparameter_type_cls) + nb::str pyname, rcl_variant_t * variant, nb::object pyparameter_cls, + nb::object pyparameter_type_cls) { int type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_NOT_SET; - py::object value = py::none(); + nb::object value = nb::none(); if (variant->bool_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_BOOL; - value = py::bool_(*(variant->bool_value)); + value = nb::bool_(*(variant->bool_value)); } else if (variant->integer_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_INTEGER; - value = py::int_(*(variant->integer_value)); + value = nb::int_(*(variant->integer_value)); } else if (variant->double_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_DOUBLE; - value = py::float_(*(variant->double_value)); + value = nb::float_(*(variant->double_value)); } else if (variant->string_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_STRING; - value = py::str(variant->string_value); + value = nb::str(variant->string_value); } else if (variant->byte_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_BYTE_ARRAY; - value = py::bytes( + value = nb::bytes( reinterpret_cast(variant->byte_array_value->values), variant->byte_array_value->size); } else if (variant->bool_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_BOOL_ARRAY; - py::list list_value = py::list(variant->bool_array_value->size); + nb::list list_value = create_sized_list(variant->bool_array_value->size); for (size_t i = 0; i < variant->bool_array_value->size; ++i) { - list_value[i] = py::bool_(variant->bool_array_value->values[i]); + list_value[i] = nb::bool_(variant->bool_array_value->values[i]); } value = list_value; } else if (variant->integer_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_INTEGER_ARRAY; - py::list list_value = py::list(variant->integer_array_value->size); + nb::list list_value = create_sized_list(variant->integer_array_value->size); for (size_t i = 0; i < variant->integer_array_value->size; ++i) { - list_value[i] = py::int_(variant->integer_array_value->values[i]); + list_value[i] = nb::int_(variant->integer_array_value->values[i]); } value = list_value; } else if (variant->double_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_DOUBLE_ARRAY; - py::list list_value = py::list(variant->double_array_value->size); + nb::list list_value = create_sized_list(variant->double_array_value->size); for (size_t i = 0; i < variant->double_array_value->size; ++i) { - list_value[i] = py::float_(variant->double_array_value->values[i]); + list_value[i] = nb::float_(variant->double_array_value->values[i]); } value = list_value; } else if (variant->string_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_STRING_ARRAY; - py::list list_value = py::list(variant->string_array_value->size); + nb::list list_value = create_sized_list(variant->string_array_value->size); for (size_t i = 0; i < variant->string_array_value->size; ++i) { - list_value[i] = py::str(variant->string_array_value->data[i]); + list_value[i] = nb::str(variant->string_array_value->data[i]); } value = list_value; } - py::object type = pyparameter_type_cls(py::int_(type_enum_value)); + nb::object type = pyparameter_type_cls(nb::int_(type_enum_value)); return pyparameter_cls(pyname, type, value); } @@ -317,8 +320,8 @@ _parameter_from_rcl_variant( */ void _populate_node_parameters_from_rcl_params( - const rcl_params_t * params, py::object pyparameter_cls, - py::object pyparameter_type_cls, py::dict pynode_params, + const rcl_params_t * params, nb::object pyparameter_cls, + nb::object pyparameter_type_cls, nb::dict pynode_params, const char * node_fqn) { for (size_t i = 0; i < params->num_nodes; ++i) { @@ -343,17 +346,17 @@ _populate_node_parameters_from_rcl_params( node_name = node_fqn; } - auto pynode_name = py::str(node_name); + auto pynode_name = nb::str(node_name.c_str()); // make a dictionary for the parameters belonging to this specific node name if (!pynode_params.contains(pynode_name)) { - pynode_params[pynode_name] = py::dict(); + pynode_params[pynode_name] = nb::dict(); } - py::dict parameter_dict = pynode_params[pynode_name]; + nb::dict parameter_dict = pynode_params[pynode_name]; rcl_node_params_t node_params = params->params[i]; for (size_t j = 0; j < node_params.num_params; ++j) { - auto pyparam_name = py::str(node_params.parameter_names[j]); + auto pyparam_name = nb::str(node_params.parameter_names[j]); parameter_dict[pyparam_name] = _parameter_from_rcl_variant( pyparam_name, &node_params.parameter_values[j], pyparameter_cls, pyparameter_type_cls); @@ -371,8 +374,8 @@ _populate_node_parameters_from_rcl_params( */ void _parse_param_overrides( - const rcl_arguments_t * args, py::object pyparameter_cls, - py::object pyparameter_type_cls, py::dict pyparams_by_node_name, + const rcl_arguments_t * args, nb::object pyparameter_cls, + nb::object pyparameter_type_cls, nb::dict pyparams_by_node_name, const char * node_fqn) { rcl_params_t * params = nullptr; @@ -386,11 +389,11 @@ _parse_param_overrides( } } -py::dict -Node::get_parameters(py::object pyparameter_cls) +nb::dict +Node::get_parameters(nb::object pyparameter_cls) { - py::dict params_by_node_name; - py::object parameter_type_cls = pyparameter_cls.attr("Type"); + nb::dict params_by_node_name; + nb::object parameter_type_cls = pyparameter_cls.attr("Type"); const rcl_node_options_t * node_options = rcl_node_get_options(rcl_node_.get()); @@ -410,8 +413,8 @@ Node::get_parameters(py::object pyparameter_cls) parameter_type_cls, params_by_node_name, node_fqn); - py::str pynode_fqn(node_fqn); - py::dict node_params; + nb::str pynode_fqn(node_fqn); + nb::dict node_params; if (params_by_node_name.contains(pynode_fqn)) { node_params = params_by_node_name[pynode_fqn]; @@ -430,10 +433,10 @@ Node::Node( const char * node_name, const char * namespace_, Context & context, - py::object pycli_args, + nb::object pycli_args, bool use_global_arguments, bool enable_rosout, - py::object rosout_qos_profile) + nb::object rosout_qos_profile) : context_(context) { rcl_ret_t ret; @@ -442,16 +445,16 @@ Node::Node( // turn the arguments into an array of C-style strings std::vector arg_values; const char ** const_arg_values = nullptr; - py::list pyargs; + nb::list pyargs; if (!pycli_args.is_none()) { - pyargs = pycli_args; + pyargs = nb::cast(pycli_args); if (!pyargs.empty()) { arg_values.resize(pyargs.size()); for (size_t i = 0; i < pyargs.size(); ++i) { // CPython owns const char * memory - no need to free it arg_values[i] = PyUnicode_AsUTF8(pyargs[i].ptr()); if (!arg_values[i]) { - throw py::error_already_set(); + throw nb::python_error(); } } const_arg_values = &(arg_values[0]); @@ -463,7 +466,7 @@ Node::Node( // use_global_arguments to False. rcl_allocator_t allocator = rcl_get_default_allocator(); if (arg_values.size() > static_cast(std::numeric_limits::max())) { - throw py::value_error("too many cli arguments given to node"); + throw nb::value_error("too many cli arguments given to node"); } int num_args = static_cast(arg_values.size()); ret = rcl_parse_arguments(num_args, const_arg_values, allocator, &arguments); @@ -512,7 +515,7 @@ Node::Node( options.enable_rosout = enable_rosout; if (!rosout_qos_profile.is_none()) { - options.rosout_qos = rosout_qos_profile.cast(); + options.rosout_qos = nb::cast(rosout_qos_profile); } ret = rcl_node_init( @@ -523,10 +526,10 @@ Node::Node( throw std::bad_alloc(); } if (RCL_RET_NODE_INVALID_NAME == ret) { - throw py::value_error(append_rcl_error("invalid node name")); + throw nb::value_error(append_rcl_error("invalid node name").c_str()); } if (RCL_RET_NODE_INVALID_NAMESPACE == ret) { - throw py::value_error(append_rcl_error("invalid node namespace")); + throw nb::value_error(append_rcl_error("invalid node namespace").c_str()); } if (RCL_RET_OK != ret) { throw RCLError("error creating node"); @@ -541,7 +544,7 @@ Node::Node( } } -py::list +nb::list Node::get_action_client_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace) { @@ -561,7 +564,7 @@ Node::get_action_client_names_and_types_by_node( return convert_to_py_names_and_types(&names_and_types); } -py::list +nb::list Node::get_action_server_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace) { @@ -581,7 +584,7 @@ Node::get_action_server_names_and_types_by_node( return convert_to_py_names_and_types(&names_and_types); } -py::list +nb::list Node::get_action_names_and_types() { // Deprecated: Use _rclpy.rclpy_get_action_names_and_types function instead @@ -596,11 +599,14 @@ Node::get_action_names_and_types() } void -define_node(py::object module) +define_node(nb::object module) { - py::class_>(module, "Node") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Node") + .def( + nb::init(), + nb::arg(), nb::arg(), nb::arg(), nb::arg().none(), nb::arg(), nb::arg(), + nb::arg().none()) + .def_prop_ro( "pointer", [](const Node & node) { return reinterpret_cast(node.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/node.hpp b/rclpy/src/rclpy/node.hpp index 32abcdf9d..8453222f0 100644 --- a/rclpy/src/rclpy/node.hpp +++ b/rclpy/src/rclpy/node.hpp @@ -15,7 +15,8 @@ #ifndef RCLPY__NODE_HPP_ #define RCLPY__NODE_HPP_ -#include +#include +#include #include @@ -24,7 +25,7 @@ #include "context.hpp" #include "destroyable.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -51,10 +52,10 @@ class Node : public Destroyable, public std::enable_shared_from_this const char * node_name, const char * namespace_, Context & context, - py::object pycli_args, + nb::object pycli_args, bool use_global_arguments, bool enable_rosout, - py::object rosout_qos_profile); + nb::object rosout_qos_profile); /// Get the fully qualified name of the node. /** @@ -159,7 +160,7 @@ class Node : public Destroyable, public std::enable_shared_from_this * \return Python list of tuples where each tuple contains the two strings: * the node name and node namespace */ - py::list + nb::list get_node_names_and_namespaces(); /// Get the list of nodes discovered by the provided node, with their respective enclaves. @@ -169,7 +170,7 @@ class Node : public Destroyable, public std::enable_shared_from_this * \return Python list of tuples where each tuple contains three strings: * node name, node namespace, and enclave. */ - py::list + nb::list get_node_names_and_namespaces_with_enclaves(); /// Get a list of parameters for the current node @@ -181,8 +182,8 @@ class Node : public Destroyable, public std::enable_shared_from_this * \param[in] pyparameter_cls The rclpy.parameter.Parameter class object. * \return A dict mapping parameter names to rclpy.parameter.Parameter (may be empty). */ - py::dict - get_parameters(py::object pyparameter_cls); + nb::dict + get_parameters(nb::object pyparameter_cls); /// Get action client names and types by node. /** @@ -190,7 +191,7 @@ class Node : public Destroyable, public std::enable_shared_from_this * \param[in] remote_node_namespace the node namespace of the actions to return * \return list of action client names and their types */ - py::list + nb::list get_action_client_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace); @@ -200,7 +201,7 @@ class Node : public Destroyable, public std::enable_shared_from_this * \param[in] remote_node_namespace the node namespace of the actions to return * \return list of action server names and their types */ - py::list + nb::list get_action_server_names_and_types_by_node( const char * remote_node_name, const char * remote_node_namespace); @@ -208,7 +209,7 @@ class Node : public Destroyable, public std::enable_shared_from_this /** * \return list of action names and types */ - py::list + nb::list get_action_names_and_types(); /// Get rcl_node_t pointer @@ -232,7 +233,7 @@ class Node : public Destroyable, public std::enable_shared_from_this * node name, node namespace, and * enclave if `get_enclaves` is true. */ - py::list + nb::list get_names_impl(bool get_enclaves); Context context_; @@ -240,6 +241,6 @@ class Node : public Destroyable, public std::enable_shared_from_this }; void -define_node(py::object module); +define_node(nb::object module); } // namespace rclpy #endif // RCLPY__NODE_HPP_ diff --git a/rclpy/src/rclpy/publisher.cpp b/rclpy/src/rclpy/publisher.cpp index e038a5ebd..dc2e6f44e 100644 --- a/rclpy/src/rclpy/publisher.cpp +++ b/rclpy/src/rclpy/publisher.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -30,20 +32,20 @@ namespace rclpy { Publisher::Publisher( - Node & node, py::object pymsg_type, std::string topic, - py::object pyqos_profile) + Node & node, nb::object pymsg_type, std::string topic, + nb::object pyqos_profile) : node_(node) { auto msg_type = static_cast( common_get_type_support(pymsg_type)); if (!msg_type) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_publisher_options_t publisher_ops = rcl_publisher_get_default_options(); if (!pyqos_profile.is_none()) { - publisher_ops.qos = pyqos_profile.cast(); + publisher_ops.qos = nb::cast(pyqos_profile); } rcl_publisher_ = std::shared_ptr( @@ -68,7 +70,7 @@ Publisher::Publisher( std::string error_text{"Failed to create publisher due to invalid topic name '"}; error_text += topic; error_text += "'"; - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } throw RCLError("Failed to create publisher"); } @@ -115,11 +117,11 @@ Publisher::get_topic_name() } void -Publisher::publish(py::object pymsg) +Publisher::publish(nb::object pymsg) { auto raw_ros_message = convert_from_py(pymsg); if (!raw_ros_message) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_ret_t ret = rcl_publish(rcl_publisher_.get(), raw_ros_message.get(), NULL); @@ -129,7 +131,7 @@ Publisher::publish(py::object pymsg) } void -Publisher::publish_raw(std::string msg) +Publisher::publish_raw(nb::bytes msg) { rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message(); serialized_msg.buffer_capacity = msg.size(); @@ -155,11 +157,13 @@ Publisher::wait_for_all_acked(rcl_duration_t pytimeout) } void -define_publisher(py::object module) +define_publisher(nb::object module) { - py::class_>(module, "Publisher") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Publisher") + .def( + nb::init(), + nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def_prop_ro( "pointer", [](const Publisher & publisher) { return reinterpret_cast(publisher.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/publisher.hpp b/rclpy/src/rclpy/publisher.hpp index e49e632c3..7c37fafb7 100644 --- a/rclpy/src/rclpy/publisher.hpp +++ b/rclpy/src/rclpy/publisher.hpp @@ -15,7 +15,9 @@ #ifndef RCLPY__PUBLISHER_HPP_ #define RCLPY__PUBLISHER_HPP_ -#include +#include +#include +#include #include #include @@ -26,7 +28,7 @@ #include "destroyable.hpp" #include "node.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -50,8 +52,8 @@ class Publisher : public Destroyable, public std::enable_shared_from_this rcl_publisher_; }; -/// Define a pybind11 wrapper for an rclpy::Publisher -void define_publisher(py::object module); +/// Define a nanobind wrapper for an rclpy::Publisher +void define_publisher(nb::object module); } // namespace rclpy #endif // RCLPY__PUBLISHER_HPP_ diff --git a/rclpy/src/rclpy/python_allocator.hpp b/rclpy/src/rclpy/python_allocator.hpp index f07559027..dc38fa1d1 100644 --- a/rclpy/src/rclpy/python_allocator.hpp +++ b/rclpy/src/rclpy/python_allocator.hpp @@ -15,7 +15,7 @@ #ifndef RCLPY__PYTHON_ALLOCATOR_HPP_ #define RCLPY__PYTHON_ALLOCATOR_HPP_ -#include +#include #include diff --git a/rclpy/src/rclpy/qos.cpp b/rclpy/src/rclpy/qos.cpp index 76778a4d6..be7e672c6 100644 --- a/rclpy/src/rclpy/qos.cpp +++ b/rclpy/src/rclpy/qos.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -164,10 +165,10 @@ predefined_qos_profile_from_name(const char * qos_profile_name) } // namespace void -define_rmw_qos_profile(py::object module) +define_rmw_qos_profile(nb::object module) { - py::class_(module, "rmw_qos_profile_t") - .def(py::init<>(&create_qos_profile)) + nb::class_(module, "rmw_qos_profile_t") + .def(nb::new_(&create_qos_profile)) .def("to_dict", &convert_to_qos_dict) .def_static("predefined", &predefined_qos_profile_from_name); } diff --git a/rclpy/src/rclpy/qos.hpp b/rclpy/src/rclpy/qos.hpp index 827cc620a..56780a5ff 100644 --- a/rclpy/src/rclpy/qos.hpp +++ b/rclpy/src/rclpy/qos.hpp @@ -15,11 +15,11 @@ #ifndef RCLPY__QOS_HPP_ #define RCLPY__QOS_HPP_ -#include +#include #include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -67,12 +67,12 @@ qos_check_compatible( const rmw_qos_profile_t & subscription_qos_profile ); -/// Define a pybind11 wrapper for an rmw_qos_profile_t +/// Define a nanobind wrapper for an rmw_qos_profile_t /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_rmw_qos_profile(py::object module); +define_rmw_qos_profile(nb::object module); } // namespace rclpy diff --git a/rclpy/src/rclpy/serialization.cpp b/rclpy/src/rclpy/serialization.cpp index 58b2c60e8..497ad5092 100644 --- a/rclpy/src/rclpy/serialization.cpp +++ b/rclpy/src/rclpy/serialization.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include @@ -50,19 +50,19 @@ SerializedMessage::~SerializedMessage() } } -py::bytes -serialize(py::object pymsg, py::object pymsg_type) +nb::bytes +serialize(nb::object pymsg, nb::object pymsg_type) { // Get type support auto ts = static_cast( common_get_type_support(pymsg_type)); if (!ts) { - throw py::error_already_set(); + throw nb::python_error(); } auto ros_msg = convert_from_py(pymsg); if (!ros_msg) { - throw py::error_already_set(); + throw nb::python_error(); } // Create a serialized message object @@ -75,19 +75,19 @@ serialize(py::object pymsg, py::object pymsg_type) } // Bundle serialized message in a bytes object - return py::bytes( + return nb::bytes( reinterpret_cast(serialized_msg.rcl_msg.buffer), serialized_msg.rcl_msg.buffer_length); } -py::object -deserialize(py::bytes pybuffer, py::object pymsg_type) +nb::object +deserialize(nb::bytes pybuffer, nb::object pymsg_type) { // Get type support auto ts = static_cast( common_get_type_support(pymsg_type)); if (!ts) { - throw py::error_already_set(); + throw nb::python_error(); } // Create a serialized message object @@ -95,11 +95,11 @@ deserialize(py::bytes pybuffer, py::object pymsg_type) // Just copy pointer to avoid extra allocation and copy char * serialized_buffer; Py_ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(pybuffer.ptr(), &serialized_buffer, &length)) { - throw py::error_already_set(); + if (PyBytes_AsStringAndSize(pybuffer.ptr(), &serialized_buffer, &length)) { + throw nb::python_error(); } if (length < 0) { - throw py::error_already_set(); + throw nb::python_error(); } serialized_msg.buffer_capacity = length; serialized_msg.buffer_length = length; @@ -107,7 +107,7 @@ deserialize(py::bytes pybuffer, py::object pymsg_type) auto deserialized_ros_msg = create_from_py(pymsg_type); if (!deserialized_ros_msg) { - throw py::error_already_set(); + throw nb::python_error(); } // Deserialize diff --git a/rclpy/src/rclpy/serialization.hpp b/rclpy/src/rclpy/serialization.hpp index 4181145b7..9499ead4a 100644 --- a/rclpy/src/rclpy/serialization.hpp +++ b/rclpy/src/rclpy/serialization.hpp @@ -15,12 +15,12 @@ #ifndef RCLPY__SERIALIZATION_HPP_ #define RCLPY__SERIALIZATION_HPP_ -#include +#include #include #include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -43,8 +43,8 @@ struct SerializedMessage * \param[in] pymsg_type the type of the ROS message * \return serialized bytes */ -py::bytes -serialize(py::object pymsg, py::object pymsg_type); +nb::bytes +serialize(nb::object pymsg, nb::object pymsg_type); /// Deserialize a ROS message /** @@ -54,8 +54,8 @@ serialize(py::object pymsg, py::object pymsg_type); * \param[in] pymsg_type the type of the ROS message to deserialize * \return an instance of a ROS message */ -py::object -deserialize(py::bytes pybuffer, py::object pymsg_type); +nb::object +deserialize(nb::bytes pybuffer, nb::object pymsg_type); } // namespace rclpy #endif // RCLPY__SERIALIZATION_HPP_ diff --git a/rclpy/src/rclpy/service.cpp b/rclpy/src/rclpy/service.cpp index 57729615a..131e08069 100644 --- a/rclpy/src/rclpy/service.cpp +++ b/rclpy/src/rclpy/service.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include +#include +#include +#include #include #include @@ -32,6 +34,8 @@ #include "utils.hpp" #include "events_executor/rcl_support.hpp" +using nb::literals::operator""_a; + namespace rclpy { using events_executor::RclEventCallbackTrampoline; @@ -48,20 +52,20 @@ Service::destroy() } Service::Service( - Node & node, py::object pysrv_type, const std::string & service_name, - py::object pyqos_profile) + Node & node, nb::object pysrv_type, const std::string & service_name, + nb::object pyqos_profile) : node_(node) { srv_type_ = static_cast( common_get_type_support(pysrv_type)); if (nullptr == srv_type_) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_service_options_t service_ops = rcl_service_get_default_options(); if (!pyqos_profile.is_none()) { - service_ops.qos = pyqos_profile.cast(); + service_ops.qos = nb::cast(pyqos_profile); } // Create a service @@ -89,7 +93,7 @@ Service::Service( error_text += "': "; error_text += rcl_get_error_string().str; rcl_reset_error(); - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } throw RCLError("failed to create service"); } @@ -101,11 +105,11 @@ Service::Service( {} void -Service::service_send_response(py::object pyresponse, rmw_request_id_t * header) +Service::service_send_response(nb::object pyresponse, rmw_request_id_t * header) { auto raw_ros_response = convert_from_py(pyresponse); if (!raw_ros_response) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_ret_t ret = rcl_send_response(rcl_service_.get(), header, raw_ros_response.get()); @@ -123,26 +127,20 @@ Service::service_send_response(py::object pyresponse, rmw_request_id_t * header) } } -py::tuple -Service::service_take_request(py::object pyrequest_type) +nb::tuple +Service::service_take_request(nb::object pyrequest_type) { auto taken_request = create_from_py(pyrequest_type); rmw_service_info_t header; - py::tuple result_tuple(2); rcl_ret_t ret = rcl_take_request_with_info(rcl_service_.get(), &header, taken_request.get()); if (ret == RCL_RET_SERVICE_TAKE_FAILED) { - result_tuple[0] = py::none(); - result_tuple[1] = py::none(); - return result_tuple; + return nb::make_tuple(nb::none(), nb::none()); } else if (RCL_RET_OK != ret) { throw RCLError("service failed to take request"); } - result_tuple[0] = convert_to_py(taken_request.get(), pyrequest_type); - result_tuple[1] = header; - - return result_tuple; + return nb::make_tuple(convert_to_py(taken_request.get(), pyrequest_type), header); } const char * @@ -151,7 +149,7 @@ Service::get_service_name() return rcl_service_get_service_name(rcl_service_.get()); } -py::dict +nb::dict Service::get_qos_profile() { const auto * options = rcl_service_get_options(rcl_service_.get()); @@ -171,13 +169,13 @@ Service::get_logger_name() const void Service::configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); pub_opts.qos = pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - pyqos_service_event_pub.cast(); + nb::cast(pyqos_service_event_pub); rcl_ret_t ret = rcl_service_configure_service_introspection( rcl_service_.get(), node_.rcl_ptr(), clock.rcl_ptr(), srv_type_, pub_opts, introspection_state); @@ -223,19 +221,21 @@ Service::clear_on_new_request_callback() } void -define_service(py::object module) +define_service(nb::object module) { - py::class_>(module, "Service") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Service") + .def( + nb::init(), + nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def_prop_ro( "pointer", [](const Service & service) { return reinterpret_cast(service.rcl_ptr()); }, "Get the address of the entity as an integer") - .def_property_readonly( + .def_prop_ro( "name", &Service::get_service_name, "Get the name of the service") - .def_property_readonly( + .def_prop_ro( "qos", &Service::get_qos_profile, "Get the qos profile of the service") .def( @@ -246,13 +246,14 @@ define_service(py::object module) "Take a request from a given service") .def( "configure_introspection", &Service::configure_introspection, + nb::arg(), nb::arg().none(), nb::arg(), "Configure whether introspection is enabled") .def( "get_logger_name", &Service::get_logger_name, "Get the name of the logger associated with the node of the service.") .def( "set_on_new_request_callback", &Service::set_on_new_request_callback, - py::arg("callback")) + "callback"_a) .def("clear_on_new_request_callback", &Service::clear_on_new_request_callback); } } // namespace rclpy diff --git a/rclpy/src/rclpy/service.hpp b/rclpy/src/rclpy/service.hpp index 34a94f3c6..fa257c784 100644 --- a/rclpy/src/rclpy/service.hpp +++ b/rclpy/src/rclpy/service.hpp @@ -15,7 +15,10 @@ #ifndef RCLPY__SERVICE_HPP_ #define RCLPY__SERVICE_HPP_ -#include +#include +#include +#include +#include #include #include @@ -30,7 +33,7 @@ #include "node.hpp" #include "utils.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -53,8 +56,8 @@ class Service : public Destroyable, public std::enable_shared_from_this * \param[in] pyqos_profile QoSProfile Python object for this service */ Service( - Node & node, py::object pysrv_type, const std::string & service_name, - py::object pyqos_profile); + Node & node, nb::object pysrv_type, const std::string & service_name, + nb::object pyqos_profile); Service( Node & node, std::shared_ptr rcl_service); @@ -70,7 +73,7 @@ class Service : public Destroyable, public std::enable_shared_from_this * \param[in] header Capsule pointing to the rmw_request_id_t header of the request we respond to */ void - service_send_response(py::object pyresponse, rmw_request_id_t * header); + service_send_response(nb::object pyresponse, rmw_request_id_t * header); /// Take a request from a given service /** @@ -82,8 +85,8 @@ class Service : public Destroyable, public std::enable_shared_from_this * first element: a Python request message with all fields populated with received request * second element: a Capsule pointing to the header (rmw_request_id) of the processed request */ - py::tuple - service_take_request(py::object pyrequest_type); + nb::tuple + service_take_request(nb::object pyrequest_type); /// Get rcl_service_t pointer rcl_service_t * @@ -106,7 +109,7 @@ class Service : public Destroyable, public std::enable_shared_from_this get_logger_name() const; /// Get the QoS profile for this service. - py::dict + nb::dict get_qos_profile(); /// Configure introspection. @@ -119,7 +122,7 @@ class Service : public Destroyable, public std::enable_shared_from_this */ void configure_introspection( - Clock & clock, py::object pyqos_service_event_pub, + Clock & clock, nb::object pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Force an early destruction of this object @@ -142,9 +145,9 @@ class Service : public Destroyable, public std::enable_shared_from_this set_callback(rcl_event_callback_t callback, const void * user_data); }; -/// Define a pybind11 wrapper for an rclpy::Service +/// Define a nanobind wrapper for an rclpy::Service void -define_service(py::object module); +define_service(nb::object module); } // namespace rclpy #endif // RCLPY__SERVICE_HPP_ diff --git a/rclpy/src/rclpy/service_info.cpp b/rclpy/src/rclpy/service_info.cpp index 162487c79..c6dc7b644 100644 --- a/rclpy/src/rclpy/service_info.cpp +++ b/rclpy/src/rclpy/service_info.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include @@ -22,15 +22,15 @@ namespace rclpy { void -define_service_info(py::object module) +define_service_info(nb::object module) { - py::class_(module, "rmw_service_info_t") - .def_readonly("source_timestamp", &rmw_service_info_t::source_timestamp) - .def_readonly("received_timestamp", &rmw_service_info_t::received_timestamp) - .def_readonly("request_id", &rmw_service_info_t::request_id); + nb::class_(module, "rmw_service_info_t") + .def_ro("source_timestamp", &rmw_service_info_t::source_timestamp) + .def_ro("received_timestamp", &rmw_service_info_t::received_timestamp) + .def_ro("request_id", &rmw_service_info_t::request_id); - py::class_(module, "rmw_request_id_t") - .def_readonly("sequence_number", &rmw_request_id_t::sequence_number); + nb::class_(module, "rmw_request_id_t") + .def_ro("sequence_number", &rmw_request_id_t::sequence_number); // "writer_guid" is not included because it's not used by rclpy } } // namespace rclpy diff --git a/rclpy/src/rclpy/service_info.hpp b/rclpy/src/rclpy/service_info.hpp index 8c9c01bcf..c10244501 100644 --- a/rclpy/src/rclpy/service_info.hpp +++ b/rclpy/src/rclpy/service_info.hpp @@ -15,17 +15,17 @@ #ifndef RCLPY__SERVICE_INFO_HPP_ #define RCLPY__SERVICE_INFO_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { -/// Define a pybind11 wrapper for an rmw_service_info_t and rmw_request_id_t +/// Define a nanobind wrapper for an rmw_service_info_t and rmw_request_id_t /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ -void define_service_info(py::object module); +void define_service_info(nb::object module); } // namespace rclpy #endif // RCLPY__SERVICE_INFO_HPP_ diff --git a/rclpy/src/rclpy/service_introspection.cpp b/rclpy/src/rclpy/service_introspection.cpp index 7d226e01e..2998d10c7 100644 --- a/rclpy/src/rclpy/service_introspection.cpp +++ b/rclpy/src/rclpy/service_introspection.cpp @@ -19,13 +19,13 @@ namespace rclpy { void -define_service_introspection(py::module module) +define_service_introspection(nb::module_ module) { - py::module m2 = module.def_submodule( + nb::module_ m2 = module.def_submodule( "service_introspection", "utilities for introspecting services"); - py::enum_(m2, "ServiceIntrospectionState") + nb::enum_(m2, "ServiceIntrospectionState", nb::is_arithmetic()) .value("OFF", RCL_SERVICE_INTROSPECTION_OFF) .value("METADATA", RCL_SERVICE_INTROSPECTION_METADATA) .value("CONTENTS", RCL_SERVICE_INTROSPECTION_CONTENTS); diff --git a/rclpy/src/rclpy/service_introspection.hpp b/rclpy/src/rclpy/service_introspection.hpp index f81e171cd..0a3c453fd 100644 --- a/rclpy/src/rclpy/service_introspection.hpp +++ b/rclpy/src/rclpy/service_introspection.hpp @@ -15,15 +15,15 @@ #ifndef RCLPY__SERVICE_INTROSPECTION_HPP_ #define RCLPY__SERVICE_INTROSPECTION_HPP_ -#include "pybind11/pybind11.h" +#include "nanobind/nanobind.h" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { void -define_service_introspection(py::module module); +define_service_introspection(nb::module_ module); } // namespace rclpy diff --git a/rclpy/src/rclpy/signal_handler.cpp b/rclpy/src/rclpy/signal_handler.cpp index bb7276312..e58ff512f 100644 --- a/rclpy/src/rclpy/signal_handler.cpp +++ b/rclpy/src/rclpy/signal_handler.cpp @@ -14,7 +14,10 @@ #include "signal_handler.hpp" -#include +#include +#include +#include +#include #include #include @@ -42,7 +45,7 @@ #endif -namespace py = pybind11; +namespace nb = nanobind; static bool trigger_guard_conditions(); static void invoke_callbacks(); @@ -445,7 +448,7 @@ check_signal_safety() "Global guard condition list access is not lock-free on this platform." "The program may deadlock when receiving SIGINT."; if (PyErr_WarnEx(PyExc_ResourceWarning, deadlock_msg, 1)) { - throw py::error_already_set(); + throw nb::python_error(); } } } @@ -470,7 +473,7 @@ register_sigint_guard_condition(const GuardCondition & guard_condition) if (NULL != guard_conditions) { while (NULL != guard_conditions[count_gcs]) { if (gc == guard_conditions[count_gcs]) { - throw py::value_error("Guard condition was already registered"); + throw nb::value_error("Guard condition was already registered"); } ++count_gcs; } @@ -526,7 +529,7 @@ unregister_sigint_guard_condition(const GuardCondition & guard_condition) } if (!found_gc) { - throw py::value_error("Guard condition was not registered"); + throw nb::value_error("Guard condition was not registered"); } rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -664,7 +667,7 @@ static void invoke_callbacks() namespace rclpy { void -define_signal_handler_api(py::module m) +define_signal_handler_api(nb::module_ m) { g_original_sigint_handler = NULL_SIGNAL_HANDLER; g_original_sigterm_handler = NULL_SIGNAL_HANDLER; @@ -684,8 +687,9 @@ define_signal_handler_api(py::module m) m.def( "uninstall_signal_handlers", &rclpy::uninstall_signal_handlers, "Uninstall rclpy signal handlers."); - py::enum_( - m, "SignalHandlerOptions", "Enum with values: `ALL`, `SIGINT`, `SIGTERM`, `NO`.") + nb::enum_( + m, "SignalHandlerOptions", nb::is_arithmetic(), + "Enum with values: `ALL`, `SIGINT`, `SIGTERM`, `NO`.") .value("ALL", SignalHandlerOptions::All) .value("NO", SignalHandlerOptions::No) .value("SIGINT", SignalHandlerOptions::SigInt) diff --git a/rclpy/src/rclpy/signal_handler.hpp b/rclpy/src/rclpy/signal_handler.hpp index e5250b8af..14c3b477c 100644 --- a/rclpy/src/rclpy/signal_handler.hpp +++ b/rclpy/src/rclpy/signal_handler.hpp @@ -15,12 +15,14 @@ #ifndef RCLPY__SIGNAL_HANDLER_HPP_ #define RCLPY__SIGNAL_HANDLER_HPP_ -#include +#include +#include +#include #include #include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -40,10 +42,10 @@ class ScopedSignalCallback { /// Define methods on a module for working with signal handlers /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_signal_handler_api(py::module module); +define_signal_handler_api(nb::module_ module); } // namespace rclpy #endif // RCLPY__SIGNAL_HANDLER_HPP_ diff --git a/rclpy/src/rclpy/subscription.cpp b/rclpy/src/rclpy/subscription.cpp index 95ccdbdee..2d5bec921 100644 --- a/rclpy/src/rclpy/subscription.cpp +++ b/rclpy/src/rclpy/subscription.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -39,7 +41,8 @@ #include "utils.hpp" #include "events_executor/rcl_support.hpp" -using pybind11::literals::operator""_a; +using nb::literals::operator""_a; + namespace rclpy { @@ -62,25 +65,25 @@ get_c_vector_string(const std::vector & strings_in) } // namespace Subscription::Subscription( - Node & node, py::object pymsg_type, std::string topic, - py::object pyqos_profile, py::object content_filter_options, - py::object acceptable_buffer_backends) + Node & node, nb::object pymsg_type, std::string topic, + nb::object pyqos_profile, nb::object content_filter_options, + nb::object acceptable_buffer_backends) : node_(node) { auto msg_type = static_cast( common_get_type_support(pymsg_type)); if (!msg_type) { - throw py::error_already_set(); + throw nb::python_error(); } rcl_subscription_options_t subscription_ops = rcl_subscription_get_default_options(); if (!pyqos_profile.is_none()) { - subscription_ops.qos = pyqos_profile.cast(); + subscription_ops.qos = nb::cast(pyqos_profile); } if (!acceptable_buffer_backends.is_none()) { - std::string acceptable_backends_str = acceptable_buffer_backends.cast(); + std::string acceptable_backends_str = nb::cast(acceptable_buffer_backends); rcl_ret_t ret = rcl_subscription_options_set_acceptable_buffer_backends( acceptable_backends_str.c_str(), &subscription_ops); @@ -106,9 +109,9 @@ Subscription::Subscription( std::string filter_expression; std::vector expression_parameters; if (!content_filter_options.is_none()) { - filter_expression = content_filter_options.attr("filter_expression").cast(); + filter_expression = nb::cast(content_filter_options.attr("filter_expression")); expression_parameters = - content_filter_options.attr("expression_parameters").cast>(); + nb::cast>(content_filter_options.attr("expression_parameters")); std::vector cstrings = get_c_vector_string(expression_parameters); rcl_ret_t ret = rcl_subscription_options_set_content_filter_options( @@ -129,7 +132,7 @@ Subscription::Subscription( std::string error_text{"Failed to create subscription due to invalid topic name '"}; error_text += topic; error_text += "'"; - throw py::value_error(error_text); + throw nb::value_error(error_text.c_str()); } throw rclpy::RCLError("Failed to create subscription"); } @@ -145,10 +148,10 @@ void Subscription::destroy() node_.destroy(); } -py::object -Subscription::take_message(py::object pymsg_type, bool raw) +nb::object +Subscription::take_message(nb::object pymsg_type, bool raw) { - py::object pytaken_msg; + nb::object pytaken_msg; rmw_message_info_t message_info; if (raw) { SerializedMessage taken{rcutils_get_default_allocator()}; @@ -160,11 +163,11 @@ Subscription::take_message(py::object pymsg_type, bool raw) throw std::bad_alloc(); } if (RCL_RET_SUBSCRIPTION_TAKE_FAILED == ret) { - return py::none(); + return nb::none(); } throw RCLError("failed to take raw message from subscription"); } - pytaken_msg = py::bytes( + pytaken_msg = nb::bytes( reinterpret_cast(taken.rcl_msg.buffer), taken.rcl_msg.buffer_length); } else { @@ -178,35 +181,40 @@ Subscription::take_message(py::object pymsg_type, bool raw) throw std::bad_alloc(); } if (RCL_RET_SUBSCRIPTION_TAKE_FAILED == ret) { - return py::none(); + return nb::none(); } throw RCLError("failed to take message from subscription"); } pytaken_msg = convert_to_py(taken_msg.get(), pymsg_type); } - py::object pub_seq_number = py::none(); + nb::object pub_seq_number = nb::none(); if (message_info.publication_sequence_number != RMW_MESSAGE_INFO_SEQUENCE_NUMBER_UNSUPPORTED) { - pub_seq_number = py::int_(message_info.publication_sequence_number); + pub_seq_number = nb::int_(message_info.publication_sequence_number); } - py::object rec_seq_number = py::none(); + nb::object rec_seq_number = nb::none(); if (message_info.reception_sequence_number != RMW_MESSAGE_INFO_SEQUENCE_NUMBER_UNSUPPORTED) { - rec_seq_number = py::int_(message_info.reception_sequence_number); + rec_seq_number = nb::int_(message_info.reception_sequence_number); } + // nanobind's dict has no keyword-argument constructor like pybind11's, but the + // Python dict builtin called with keyword arguments gets the same result + nb::object pydict = nb::module_::import_("builtins").attr("dict"); + // Convert publisher_gid to Python dict with implementation_identifier and data - py::object publisher_gid = py::none(); + nb::object publisher_gid = nb::none(); if (message_info.publisher_gid.implementation_identifier != nullptr) { - publisher_gid = py::dict( - "implementation_identifier"_a = py::str(message_info.publisher_gid.implementation_identifier), - "data"_a = py::bytes( + publisher_gid = pydict( + "implementation_identifier"_a = + nb::str(message_info.publisher_gid.implementation_identifier), + "data"_a = nb::bytes( reinterpret_cast(message_info.publisher_gid.data), RMW_GID_STORAGE_SIZE) ); } - return py::make_tuple( - pytaken_msg, py::dict( + return nb::make_tuple( + pytaken_msg, pydict( "source_timestamp"_a = message_info.source_timestamp, "received_timestamp"_a = message_info.received_timestamp, "publication_sequence_number"_a = pub_seq_number, @@ -332,7 +340,7 @@ Subscription::set_content_filter( } } -py::object +nb::object Subscription::get_content_filter() const { rcl_subscription_content_filter_options_t options = @@ -363,8 +371,8 @@ Subscription::get_content_filter() const expression_parameters.push_back(content_filter_options.expression_parameters.data[i]); } - py::object content_filter_options_class = - py::module_::import("rclpy.subscription_content_filter_options").attr("ContentFilterOptions"); + nb::object content_filter_options_class = + nb::module_::import_("rclpy.subscription_content_filter_options").attr("ContentFilterOptions"); return content_filter_options_class( std::string(content_filter_options.filter_expression), @@ -372,17 +380,17 @@ Subscription::get_content_filter() const } void -define_subscription(py::object module) +define_subscription(nb::object module) { - py::class_>(module, "Subscription") - .def(py::init(), - py::arg("node"), - py::arg("msg_type"), - py::arg("topic"), - py::arg("qos_profile"), - py::arg("content_filter_options") = py::none(), - py::arg("acceptable_buffer_backends") = py::none()) - .def_property_readonly( + nb::class_(module, "Subscription") + .def(nb::init(), + "node"_a, + "msg_type"_a, + "topic"_a, + "qos_profile"_a.none(), + "content_filter_options"_a = nb::none(), + "acceptable_buffer_backends"_a = nb::none()) + .def_prop_ro( "pointer", [](const Subscription & subscription) { return reinterpret_cast(subscription.rcl_ptr()); }, @@ -401,7 +409,7 @@ define_subscription(py::object module) "Count the publishers from a subscription.") .def( "set_on_new_message_callback", &Subscription::set_on_new_message_callback, - py::arg("callback")) + "callback"_a) .def("clear_on_new_message_callback", &Subscription::clear_on_new_message_callback) .def("is_cft_supported", &Subscription::is_cft_supported, "Check if subscription instance supports content filtering.") diff --git a/rclpy/src/rclpy/subscription.hpp b/rclpy/src/rclpy/subscription.hpp index af7965646..7b495adb2 100644 --- a/rclpy/src/rclpy/subscription.hpp +++ b/rclpy/src/rclpy/subscription.hpp @@ -15,7 +15,11 @@ #ifndef RCLPY__SUBSCRIPTION_HPP_ #define RCLPY__SUBSCRIPTION_HPP_ -#include +#include +#include +#include +#include +#include #include @@ -28,7 +32,7 @@ #include "destroyable.hpp" #include "node.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -52,9 +56,9 @@ class Subscription : public Destroyable, public std::enable_shared_from_this +#include #include @@ -31,11 +31,11 @@ create_time_point(int64_t nanoseconds, int clock_type) } void -define_time_point(py::object module) +define_time_point(nb::object module) { - py::class_(module, "rcl_time_point_t") - .def(py::init<>(&create_time_point)) - .def_readonly("nanoseconds", &rcl_time_point_t::nanoseconds) - .def_readonly("clock_type", &rcl_time_point_t::clock_type); + nb::class_(module, "rcl_time_point_t") + .def(nb::new_(&create_time_point)) + .def_ro("nanoseconds", &rcl_time_point_t::nanoseconds) + .def_ro("clock_type", &rcl_time_point_t::clock_type); } } // namespace rclpy diff --git a/rclpy/src/rclpy/time_point.hpp b/rclpy/src/rclpy/time_point.hpp index 4f80f0d17..e53e968b6 100644 --- a/rclpy/src/rclpy/time_point.hpp +++ b/rclpy/src/rclpy/time_point.hpp @@ -15,17 +15,17 @@ #ifndef RCLPY__TIME_POINT_HPP_ #define RCLPY__TIME_POINT_HPP_ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { -/// Define a pybind11 wrapper for an rcl_time_point_t +/// Define a nanobind wrapper for an rcl_time_point_t /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ -void define_time_point(py::object module); +void define_time_point(nb::object module); } // namespace rclpy #endif // RCLPY__TIME_POINT_HPP_ diff --git a/rclpy/src/rclpy/timer.cpp b/rclpy/src/rclpy/timer.cpp index 405b576a5..74ffcb85b 100644 --- a/rclpy/src/rclpy/timer.cpp +++ b/rclpy/src/rclpy/timer.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -31,6 +33,8 @@ #include "utils.hpp" #include "events_executor/rcl_support.hpp" +using nb::literals::operator""_a; + namespace rclpy { using events_executor::RclEventCallbackTrampoline; @@ -102,10 +106,10 @@ void Timer::call_timer() } } -py::object +nb::object Timer::call_timer_with_info() { - py::dict timer_info; + nb::dict timer_info; rcl_timer_call_info_t call_info; rcl_ret_t ret = rcl_timer_call_with_info(rcl_timer_.get(), &call_info); if (ret != RCL_RET_OK) { @@ -219,11 +223,11 @@ Timer::clear_on_reset_callback() } void -define_timer(py::object module) +define_timer(nb::object module) { - py::class_>(module, "Timer") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "Timer") + .def(nb::init()) + .def_prop_ro( "pointer", [](const Timer & timer) { return reinterpret_cast(timer.rcl_ptr()); }, @@ -258,7 +262,7 @@ define_timer(py::object module) "Check if a timer is canceled.") .def( "set_on_reset_callback", &Timer::set_on_reset_callback, - py::arg("callback")) + "callback"_a) .def("clear_on_reset_callback", &Timer::clear_on_reset_callback); } diff --git a/rclpy/src/rclpy/timer.hpp b/rclpy/src/rclpy/timer.hpp index f7027f9f6..c253edde3 100644 --- a/rclpy/src/rclpy/timer.hpp +++ b/rclpy/src/rclpy/timer.hpp @@ -15,7 +15,10 @@ #ifndef RCLPY__TIMER_HPP_ #define RCLPY__TIMER_HPP_ -#include +#include +#include +#include +#include #include @@ -27,7 +30,7 @@ #include "context.hpp" #include "destroyable.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -80,7 +83,7 @@ class Timer : public Destroyable, public std::enable_shared_from_this * * \return the actual and expected call time. */ - py::object + nb::object call_timer_with_info(); /// Update the timer period @@ -161,12 +164,12 @@ class Timer : public Destroyable, public std::enable_shared_from_this set_callback(rcl_event_callback_t callback, const void * user_data); }; -/// Define a pybind11 wrapper for an rcl_timer_t +/// Define a nanobind wrapper for an rcl_timer_t /** - * \param[in] module a pybind11 module to add the definition to + * \param[in] module a nanobind module to add the definition to */ void -define_timer(py::object module); +define_timer(nb::object module); } // namespace rclpy #endif // RCLPY__TIMER_HPP_ diff --git a/rclpy/src/rclpy/type_description_service.cpp b/rclpy/src/rclpy/type_description_service.cpp index d10bd8f95..226ccd175 100644 --- a/rclpy/src/rclpy/type_description_service.cpp +++ b/rclpy/src/rclpy/type_description_service.cpp @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include #include #include @@ -42,9 +43,9 @@ Service TypeDescriptionService::get_impl() return *service_; } -py::object TypeDescriptionService::handle_request( - py::object pyrequest, - py::object pyresponse_type, +nb::object TypeDescriptionService::handle_request( + nb::object pyrequest, + nb::object pyresponse_type, Node & node) { // Header not used by handler, just needed as part of signature. @@ -65,12 +66,11 @@ void TypeDescriptionService::destroy() } void -define_type_description_service(py::object module) +define_type_description_service(nb::object module) { - py::class_ - >(module, "TypeDescriptionService") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "TypeDescriptionService") + .def(nb::init()) + .def_prop_ro( "impl", &TypeDescriptionService::get_impl, "Get the rcl service wrapper capsule.") .def( "handle_request", &TypeDescriptionService::handle_request, diff --git a/rclpy/src/rclpy/type_description_service.hpp b/rclpy/src/rclpy/type_description_service.hpp index abc35ed61..bb2cbc9c4 100644 --- a/rclpy/src/rclpy/type_description_service.hpp +++ b/rclpy/src/rclpy/type_description_service.hpp @@ -15,14 +15,15 @@ #ifndef RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ #define RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ -#include +#include +#include #include #include "destroyable.hpp" #include "service.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -53,8 +54,8 @@ class TypeDescriptionService * \param[in] node The node that this service belongs to * \return response message to send */ - py::object - handle_request(py::object pyrequest, py::object pyresponse_type, Node & node); + nb::object + handle_request(nb::object pyrequest, nb::object pyresponse_type, Node & node); /// Force early cleanup of object void @@ -64,9 +65,9 @@ class TypeDescriptionService std::shared_ptr service_; }; -/// Define a pybind11 wrapper for an rclpy::TypeDescriptionService +/// Define a nanobind wrapper for an rclpy::TypeDescriptionService void -define_type_description_service(py::object module); +define_type_description_service(nb::object module); } // namespace rclpy #endif // RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ diff --git a/rclpy/src/rclpy/utils.cpp b/rclpy/src/rclpy/utils.cpp index 3a1cd5a4f..3bd148945 100644 --- a/rclpy/src/rclpy/utils.cpp +++ b/rclpy/src/rclpy/utils.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -39,55 +41,55 @@ namespace rclpy { -py::list +nb::list convert_to_py_names_and_types(const rcl_names_and_types_t * names_and_types) { assert(names_and_types); - py::list py_names_and_types(names_and_types->names.size); + nb::list py_names_and_types = create_sized_list(names_and_types->names.size); for (size_t i = 0u; i < names_and_types->names.size; ++i) { - py::list py_types(names_and_types->types[i].size); + nb::list py_types = create_sized_list(names_and_types->types[i].size); for (size_t j = 0u; j < names_and_types->types[i].size; ++j) { - py_types[j] = py::str(names_and_types->types[i].data[j]); + py_types[j] = nb::str(names_and_types->types[i].data[j]); } - py_names_and_types[i] = py::make_tuple( - py::str(names_and_types->names.data[i]), py_types); + py_names_and_types[i] = nb::make_tuple( + nb::str(names_and_types->names.data[i]), py_types); } return py_names_and_types; } void * -common_get_type_support(py::object pymessage) +common_get_type_support(nb::object pymessage) { - py::object pymetaclass = pymessage.attr("__class__"); + nb::object pymetaclass = pymessage.attr("__class__"); - py::object value = pymetaclass.attr("_TYPE_SUPPORT"); - auto capsule_ptr = static_cast(value.cast()); + nb::object value = pymetaclass.attr("_TYPE_SUPPORT"); + void * capsule_ptr = nb::cast(value).data(); return capsule_ptr; } std::unique_ptr -create_from_py(py::object pymessage) +create_from_py(nb::object pymessage) { typedef void * create_ros_message_function (void); - py::object pymetaclass = pymessage.attr("__class__"); + nb::object pymetaclass = pymessage.attr("__class__"); - py::object value = pymetaclass.attr("_CREATE_ROS_MESSAGE"); - auto capsule_ptr = static_cast(value.cast()); + nb::object value = pymetaclass.attr("_CREATE_ROS_MESSAGE"); + void * capsule_ptr = nb::cast(value).data(); auto create_ros_message = reinterpret_cast(capsule_ptr); if (!create_ros_message) { - throw py::error_already_set(); + throw nb::python_error(); } value = pymetaclass.attr("_DESTROY_ROS_MESSAGE"); - capsule_ptr = static_cast(value.cast()); + capsule_ptr = nb::cast(value).data(); auto destroy_ros_message = reinterpret_cast(capsule_ptr); if (!destroy_ros_message) { - throw py::error_already_set(); + throw nb::python_error(); } void * message = create_ros_message(); @@ -99,44 +101,44 @@ create_from_py(py::object pymessage) } std::unique_ptr -convert_from_py(py::object pymessage) +convert_from_py(nb::object pymessage) { typedef bool convert_from_py_signature (PyObject *, void *); std::unique_ptr message = create_from_py(pymessage); - py::object pymetaclass = pymessage.attr("__class__"); + nb::object pymetaclass = pymessage.attr("__class__"); - auto capsule_ptr = static_cast( - pymetaclass.attr("_CONVERT_FROM_PY").cast()); + void * capsule_ptr = + nb::cast(pymetaclass.attr("_CONVERT_FROM_PY")).data(); auto convert = reinterpret_cast(capsule_ptr); if (!convert) { - throw py::error_already_set(); + throw nb::python_error(); } if (!convert(pymessage.ptr(), message.get())) { - throw py::error_already_set(); + throw nb::python_error(); } return message; } -py::object -convert_to_py(void * message, py::object pyclass) +nb::object +convert_to_py(void * message, nb::object pyclass) { - py::object pymetaclass = pyclass.attr("__class__"); + nb::object pymetaclass = pyclass.attr("__class__"); - auto capsule_ptr = static_cast( - pymetaclass.attr("_CONVERT_TO_PY").cast()); + void * capsule_ptr = + nb::cast(pymetaclass.attr("_CONVERT_TO_PY")).data(); typedef PyObject * convert_to_py_function (void *); auto convert = reinterpret_cast(capsule_ptr); if (!convert) { - throw py::error_already_set(); + throw nb::python_error(); } - return py::reinterpret_steal(convert(message)); + return nb::steal(convert(message)); } const char * @@ -153,8 +155,8 @@ assert_liveliness(rclpy::Publisher * publisher) } } -py::list -remove_ros_args(py::object pycli_args) +nb::list +remove_ros_args(nb::object pycli_args) { rcl_ret_t ret; rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -162,16 +164,16 @@ remove_ros_args(py::object pycli_args) std::vector arg_values; const char ** const_arg_values = NULL; - py::list pyargs; + nb::list pyargs; if (!pycli_args.is_none()) { - pyargs = pycli_args; + pyargs = nb::cast(pycli_args); if (!pyargs.empty()) { arg_values.resize(pyargs.size()); for (size_t i = 0; i < pyargs.size(); ++i) { // CPython owns const char * memory - no need to free it arg_values[i] = PyUnicode_AsUTF8(pyargs[i].ptr()); if (!arg_values[i]) { - throw py::error_already_set(); + throw nb::python_error(); } } const_arg_values = &(arg_values[0]); @@ -179,7 +181,7 @@ remove_ros_args(py::object pycli_args) } if (arg_values.size() > static_cast(std::numeric_limits::max())) { - throw py::value_error("too many cli arguments"); + throw nb::value_error("too many cli arguments"); } int num_args = static_cast(arg_values.size()); @@ -235,7 +237,7 @@ remove_ros_args(py::object pycli_args) #pragma warning(pop) #endif - py::list result_args(nonros_argc); + nb::list result_args = create_sized_list(nonros_argc); for (int i = 0; i < nonros_argc; ++i) { result_args[i] = nonros_argv[i]; } @@ -244,7 +246,7 @@ remove_ros_args(py::object pycli_args) } void -throw_if_unparsed_ros_args(py::list pyargs, const rcl_arguments_t & rcl_args) +throw_if_unparsed_ros_args(nb::list pyargs, const rcl_arguments_t & rcl_args) { int unparsed_ros_args_count = rcl_arguments_get_count_unparsed_ros(&rcl_args); @@ -265,7 +267,7 @@ throw_if_unparsed_ros_args(py::list pyargs, const rcl_arguments_t & rcl_args) RCPPUTILS_SCOPE_EXIT(allocator.deallocate(unparsed_indices_c, allocator.state)); - py::list unparsed_args; + nb::list unparsed_args; for (int i = 0; i < unparsed_ros_args_count; ++i) { int index = unparsed_indices_c[i]; if (index < 0 || static_cast(index) >= pyargs.size()) { @@ -274,13 +276,13 @@ throw_if_unparsed_ros_args(py::list pyargs, const rcl_arguments_t & rcl_args) unparsed_args.append(pyargs[index]); } - throw UnknownROSArgsError(static_cast(py::repr(unparsed_args))); + throw UnknownROSArgsError(nb::repr(unparsed_args).c_str()); } -py::dict +nb::dict rclpy_action_get_rmw_qos_profile(const char * rmw_profile) { - py::dict pyqos_profile; + nb::dict pyqos_profile; if (0 == strcmp(rmw_profile, "rcl_action_qos_profile_status_default")) { pyqos_profile = convert_to_qos_dict(&rcl_action_qos_profile_status_default); } else { @@ -291,25 +293,25 @@ rclpy_action_get_rmw_qos_profile(const char * rmw_profile) return pyqos_profile; } -py::dict +nb::dict _convert_to_py_topic_endpoint_info(const rmw_topic_endpoint_info_t * topic_endpoint_info) { - py::list py_endpoint_gid = py::list(RMW_GID_STORAGE_SIZE); + nb::list py_endpoint_gid = create_sized_list(RMW_GID_STORAGE_SIZE); for (size_t i = 0; i < RMW_GID_STORAGE_SIZE; i++) { - py_endpoint_gid[i] = py::int_(topic_endpoint_info->endpoint_gid[i]); + py_endpoint_gid[i] = nb::int_(topic_endpoint_info->endpoint_gid[i]); } // Create dictionary that represents rmw_topic_endpoint_info_t - py::dict py_endpoint_info_dict; + nb::dict py_endpoint_info_dict; // Populate keyword arguments // A success returns 0, and a failure returns -1 - py_endpoint_info_dict["node_name"] = py::str(topic_endpoint_info->node_name); - py_endpoint_info_dict["node_namespace"] = py::str(topic_endpoint_info->node_namespace); - py_endpoint_info_dict["topic_type"] = py::str(topic_endpoint_info->topic_type); + py_endpoint_info_dict["node_name"] = nb::str(topic_endpoint_info->node_name); + py_endpoint_info_dict["node_namespace"] = nb::str(topic_endpoint_info->node_namespace); + py_endpoint_info_dict["topic_type"] = nb::str(topic_endpoint_info->topic_type); py_endpoint_info_dict["topic_type_hash"] = convert_to_type_hash_dict(&topic_endpoint_info->topic_type_hash); py_endpoint_info_dict["endpoint_type"] = - py::int_(static_cast(topic_endpoint_info->endpoint_type)); + nb::int_(static_cast(topic_endpoint_info->endpoint_type)); py_endpoint_info_dict["endpoint_gid"] = py_endpoint_gid; py_endpoint_info_dict["qos_profile"] = convert_to_qos_dict(&topic_endpoint_info->qos_profile); @@ -317,14 +319,14 @@ _convert_to_py_topic_endpoint_info(const rmw_topic_endpoint_info_t * topic_endpo return py_endpoint_info_dict; } -py::list +nb::list convert_to_py_topic_endpoint_info_list(const rmw_topic_endpoint_info_array_t * info_array) { if (!info_array) { throw std::runtime_error("rmw_topic_endpoint_info_array_t pointer is empty"); } - py::list py_info_array(info_array->size); + nb::list py_info_array = create_sized_list(info_array->size); for (size_t i = 0; i < info_array->size; ++i) { rmw_topic_endpoint_info_t topic_endpoint_info = info_array->info_array[i]; @@ -334,30 +336,30 @@ convert_to_py_topic_endpoint_info_list(const rmw_topic_endpoint_info_array_t * i return py_info_array; } -py::dict +nb::dict _convert_to_py_service_endpoint_info(const rmw_service_endpoint_info_t * service_endpoint_info) { - py::list py_endpoint_gids; + nb::list py_endpoint_gids; for(size_t c = 0; c < service_endpoint_info->endpoint_count; c++) { - py::list py_endpoint_gid = py::list(RMW_GID_STORAGE_SIZE); + nb::list py_endpoint_gid = create_sized_list(RMW_GID_STORAGE_SIZE); for (size_t i = 0; i < RMW_GID_STORAGE_SIZE; i++) { - py_endpoint_gid[i] = py::int_(service_endpoint_info->endpoint_gids[c][i]); + py_endpoint_gid[i] = nb::int_(service_endpoint_info->endpoint_gids[c][i]); } py_endpoint_gids.append(py_endpoint_gid); } // Create dictionary that represents rmw_service_endpoint_info_t - py::dict py_endpoint_info_dict; + nb::dict py_endpoint_info_dict; // Populate keyword arguments // A success returns 0, and a failure returns -1 - py_endpoint_info_dict["node_name"] = py::str(service_endpoint_info->node_name); - py_endpoint_info_dict["node_namespace"] = py::str(service_endpoint_info->node_namespace); - py_endpoint_info_dict["service_type"] = py::str(service_endpoint_info->service_type); + py_endpoint_info_dict["node_name"] = nb::str(service_endpoint_info->node_name); + py_endpoint_info_dict["node_namespace"] = nb::str(service_endpoint_info->node_namespace); + py_endpoint_info_dict["service_type"] = nb::str(service_endpoint_info->service_type); py_endpoint_info_dict["service_type_hash"] = convert_to_type_hash_dict(&service_endpoint_info->service_type_hash); - py_endpoint_info_dict["qos_profiles"] = py::list(); - py_endpoint_info_dict["endpoint_gids"] = py::list(); + py_endpoint_info_dict["qos_profiles"] = nb::list(); + py_endpoint_info_dict["endpoint_gids"] = nb::list(); - py::list qos_profiles_list; + nb::list qos_profiles_list; // Append values to the lists for (size_t i = 0; i < service_endpoint_info->endpoint_count; i++) { qos_profiles_list.append( @@ -367,20 +369,20 @@ _convert_to_py_service_endpoint_info(const rmw_service_endpoint_info_t * service py_endpoint_info_dict["qos_profiles"] = qos_profiles_list; py_endpoint_info_dict["endpoint_gids"] = py_endpoint_gids; py_endpoint_info_dict["endpoint_type"] = - py::int_(static_cast(service_endpoint_info->endpoint_type)); - py_endpoint_info_dict["endpoint_count"] = py::int_(service_endpoint_info->endpoint_count); + nb::int_(static_cast(service_endpoint_info->endpoint_type)); + py_endpoint_info_dict["endpoint_count"] = nb::int_(service_endpoint_info->endpoint_count); return py_endpoint_info_dict; } -py::list +nb::list convert_to_py_service_endpoint_info_list(const rmw_service_endpoint_info_array_t * info_array) { if (!info_array) { throw std::runtime_error("rmw_service_endpoint_info_array_t pointer is empty"); } - py::list py_info_array(info_array->size); + nb::list py_info_array = create_sized_list(info_array->size); for (size_t i = 0; i < info_array->size; ++i) { rmw_service_endpoint_info_t service_endpoint_info = info_array->info_array[i]; @@ -390,45 +392,45 @@ convert_to_py_service_endpoint_info_list(const rmw_service_endpoint_info_array_t return py_info_array; } -py::object +nb::object _convert_to_py_action_endpoint_info(const rcl_action_endpoint_info_t * action_endpoint_info) { // Create dictionary that represents the aggregated endpoint information of // all the underlying entities of one action client or one action server. // Sub-entities that have not been discovered are represented as None. - py::dict py_endpoint_info_dict; + nb::dict py_endpoint_info_dict; py_endpoint_info_dict["goal_service_info"] = action_endpoint_info->goal_service_info.node_name ? - py::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->goal_service_info)) : - py::object(py::none()); + nb::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->goal_service_info)) : + nb::object(nb::none()); py_endpoint_info_dict["cancel_service_info"] = action_endpoint_info->cancel_service_info.node_name ? - py::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->cancel_service_info)) : - py::object(py::none()); + nb::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->cancel_service_info)) : + nb::object(nb::none()); py_endpoint_info_dict["result_service_info"] = action_endpoint_info->result_service_info.node_name ? - py::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->result_service_info)) : - py::object(py::none()); + nb::object(_convert_to_py_service_endpoint_info(&action_endpoint_info->result_service_info)) : + nb::object(nb::none()); py_endpoint_info_dict["feedback_topic_info"] = action_endpoint_info->feedback_topic_info.node_name ? - py::object(_convert_to_py_topic_endpoint_info(&action_endpoint_info->feedback_topic_info)) : - py::object(py::none()); + nb::object(_convert_to_py_topic_endpoint_info(&action_endpoint_info->feedback_topic_info)) : + nb::object(nb::none()); py_endpoint_info_dict["status_topic_info"] = action_endpoint_info->status_topic_info.node_name ? - py::object(_convert_to_py_topic_endpoint_info(&action_endpoint_info->status_topic_info)) : - py::object(py::none()); + nb::object(_convert_to_py_topic_endpoint_info(&action_endpoint_info->status_topic_info)) : + nb::object(nb::none()); return py_endpoint_info_dict; } -py::list +nb::list convert_to_py_action_endpoint_info_list(const rcl_action_endpoint_info_array_t * info_array) { if (!info_array) { throw std::runtime_error("rcl_action_endpoint_info_array_t pointer is empty"); } - py::list py_info_array(info_array->size); + nb::list py_info_array = create_sized_list(info_array->size); for (size_t i = 0; i < info_array->size; ++i) { // add this dict to the list @@ -438,45 +440,45 @@ convert_to_py_action_endpoint_info_list(const rcl_action_endpoint_info_array_t * } static -py::object +nb::object _convert_rmw_time_to_py_duration(const rmw_time_t * duration) { - auto pyduration_module = py::module::import("rclpy.duration"); - py::object pymetaclass = pyduration_module.attr("Duration"); + auto pyduration_module = nb::module_::import_("rclpy.duration"); + nb::object pymetaclass = pyduration_module.attr("Duration"); // to bring in the `_a` literal - using namespace pybind11::literals; // NOLINT + using nb::literals::operator""_a; return pymetaclass("seconds"_a = duration->sec, "nanoseconds"_a = duration->nsec); } -py::dict +nb::dict convert_to_qos_dict(const rmw_qos_profile_t * qos_profile) { // Create dictionary and populate arguments with QoSProfile object - py::dict pyqos_kwargs; + nb::dict pyqos_kwargs; - pyqos_kwargs["depth"] = py::int_(qos_profile->depth); - pyqos_kwargs["history"] = py::int_(static_cast(qos_profile->history)); - pyqos_kwargs["reliability"] = py::int_(static_cast(qos_profile->reliability)); - pyqos_kwargs["durability"] = py::int_(static_cast(qos_profile->durability)); + pyqos_kwargs["depth"] = nb::int_(qos_profile->depth); + pyqos_kwargs["history"] = nb::int_(static_cast(qos_profile->history)); + pyqos_kwargs["reliability"] = nb::int_(static_cast(qos_profile->reliability)); + pyqos_kwargs["durability"] = nb::int_(static_cast(qos_profile->durability)); pyqos_kwargs["lifespan"] = _convert_rmw_time_to_py_duration(&qos_profile->lifespan); pyqos_kwargs["deadline"] = _convert_rmw_time_to_py_duration(&qos_profile->deadline); - pyqos_kwargs["liveliness"] = py::int_(static_cast(qos_profile->liveliness)); + pyqos_kwargs["liveliness"] = nb::int_(static_cast(qos_profile->liveliness)); pyqos_kwargs["liveliness_lease_duration"] = _convert_rmw_time_to_py_duration( &qos_profile->liveliness_lease_duration); pyqos_kwargs["avoid_ros_namespace_conventions"] = - py::bool_(qos_profile->avoid_ros_namespace_conventions); + nb::bool_(qos_profile->avoid_ros_namespace_conventions); return pyqos_kwargs; } -py::dict +nb::dict convert_to_type_hash_dict(const rosidl_type_hash_t * type_hash) { // Create dictionary and populate arguments with type hash object - py::dict type_hash_kwargs; + nb::dict type_hash_kwargs; - type_hash_kwargs["version"] = py::int_(type_hash->version); - type_hash_kwargs["value"] = py::bytes( + type_hash_kwargs["version"] = nb::int_(type_hash->version); + type_hash_kwargs["value"] = nb::bytes( reinterpret_cast(type_hash->value), ROSIDL_TYPE_HASH_SIZE); diff --git a/rclpy/src/rclpy/utils.hpp b/rclpy/src/rclpy/utils.hpp index 545c580c3..0fbc9e83f 100644 --- a/rclpy/src/rclpy/utils.hpp +++ b/rclpy/src/rclpy/utils.hpp @@ -15,7 +15,7 @@ #ifndef RCLPY__UTILS_HPP_ #define RCLPY__UTILS_HPP_ -#include +#include #include #include // rcl_names_and_types_t @@ -27,20 +27,42 @@ #include "publisher.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { typedef void destroy_ros_message_function (void *); +/// Create a Python list of a fixed size, with every item set to None. +/** + * nanobind's nb::list lacks pybind11's presized list constructor, but the same + * thing can be done with the C API. The items are initialized to None rather + * than left NULL, because nanobind's item assignment is not NULL-safe. + * + * \param[in] size The number of items the list will hold. + * \return A list of the requested size. + */ +inline nb::list +create_sized_list(size_t size) +{ + PyObject * list = PyList_New(static_cast(size)); + if (!list) { + throw nb::python_error(); + } + for (size_t i = 0; i < size; ++i) { + PyList_SET_ITEM(list, static_cast(i), Py_NewRef(Py_None)); + } + return nb::steal(list); +} + /// Convert a C rcl_names_and_types_t into a Python list. /** * \param[in] topic_names_and_types The names and types struct to convert. * \return List of tuples, where the first element of each tuple is a string * for the name and the second element is a list of strings for the types. */ -py::list +nb::list convert_to_py_names_and_types(const rcl_names_and_types_t * topic_names_and_types); /// Get the type support structure for a Python ROS message type. @@ -49,7 +71,7 @@ convert_to_py_names_and_types(const rcl_names_and_types_t * topic_names_and_type * \return The type support structure or NULL if an error occurred. */ void * -common_get_type_support(py::object pymessage); +common_get_type_support(nb::object pymessage); /// Create the equivalent ROS message C type instance for a given Python type. /** @@ -59,7 +81,7 @@ common_get_type_support(py::object pymessage); * \return a ROS message C type instance. */ std::unique_ptr -create_from_py(py::object pyclass); +create_from_py(nb::object pyclass); /// Convert a ROS message from a Python type to a C type. /** @@ -69,7 +91,7 @@ create_from_py(py::object pyclass); * \return unique pointer with the C version of the input ROS message. */ std::unique_ptr -convert_from_py(py::object pyclass); +convert_from_py(nb::object pyclass); /// Convert a ROS message from a C type to a Python type. /** @@ -79,8 +101,8 @@ convert_from_py(py::object pyclass); * \param[in] pyclass ROS message Python type to convert to. * \return an instance of \p pyclass. */ -py::object -convert_to_py(void * message, py::object pyclass); +nb::object +convert_to_py(void * message, nb::object pyclass); /// Return the identifier of the current rmw_implementation /** @@ -114,12 +136,12 @@ assert_liveliness(rclpy::Publisher * publisher); * \param[in] pycli_args A list of strings * \return Parsed list of strings */ -py::list -remove_ros_args(py::object pycli_args); +nb::list +remove_ros_args(nb::object pycli_args); /// Throw UnparsedROSArgsError with a message saying which args are unparsed. void -throw_if_unparsed_ros_args(py::list pyargs, const rcl_arguments_t & rcl_args); +throw_if_unparsed_ros_args(nb::list pyargs, const rcl_arguments_t & rcl_args); /// Fetch a predefined qos_profile from rcl_action and convert it to a Python QoSProfile object. /** @@ -130,7 +152,7 @@ throw_if_unparsed_ros_args(py::list pyargs, const rcl_arguments_t & rcl_args); * \param[in] rmw_profile String with the name of the profile to load. * \return QoSProfile object. */ -py::dict +nb::dict rclpy_action_get_rmw_qos_profile(const char * rmw_profile); /// Convert a C rmw_topic_endpoint_info_array_t into a Python list. @@ -140,7 +162,7 @@ rclpy_action_get_rmw_qos_profile(const char * rmw_profile); * \param[in] info_array a pointer to a rmw_topic_endpoint_info_array_t * \return Python list */ -py::list +nb::list convert_to_py_topic_endpoint_info_list(const rmw_topic_endpoint_info_array_t * info_array); /// Convert a C rmw_service_endpoint_info_array_t into a Python list. @@ -150,7 +172,7 @@ convert_to_py_topic_endpoint_info_list(const rmw_topic_endpoint_info_array_t * i * \param[in] info_array a pointer to a rmw_service_endpoint_info_array_t * \return Python list */ -py::list +nb::list convert_to_py_service_endpoint_info_list(const rmw_service_endpoint_info_array_t * info_array); /// Convert a C rcl_action_endpoint_info_array_t into a Python list. @@ -166,7 +188,7 @@ convert_to_py_service_endpoint_info_list(const rmw_service_endpoint_info_array_t * \param[in] info_array a pointer to a rcl_action_endpoint_info_array_t * \return Python list */ -py::list +nb::list convert_to_py_action_endpoint_info_list(const rcl_action_endpoint_info_array_t * info_array); /// Convert a C rmw_qos_profile_t into a Python dictionary with qos profile args. @@ -174,7 +196,7 @@ convert_to_py_action_endpoint_info_list(const rcl_action_endpoint_info_array_t * * \param[in] qos_profile Pointer to a rmw_qos_profile_t to convert * \return Python dictionary */ -py::dict +nb::dict convert_to_qos_dict(const rmw_qos_profile_t * qos_profile); /// Convert a C rosidl_type_hash_t into a Python dictionary. @@ -182,7 +204,7 @@ convert_to_qos_dict(const rmw_qos_profile_t * qos_profile); * \param[in] type_hash Pointer to a rosidl_type_hash_t to convert * \return Python dictionary */ -py::dict +nb::dict convert_to_type_hash_dict(const rosidl_type_hash_t * type_hash); /// Issue a Python RuntimeWarning for an rcl fini failure and reset the rcl error. diff --git a/rclpy/src/rclpy/wait_set.cpp b/rclpy/src/rclpy/wait_set.cpp index 48b4d41b7..131ebcb0d 100644 --- a/rclpy/src/rclpy/wait_set.cpp +++ b/rclpy/src/rclpy/wait_set.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +#include +#include #include #include @@ -193,10 +195,10 @@ WaitSet::is_ready(const std::string & entity_type, size_t index) } template -py::list +nb::list _get_ready_entities(const EntityArray ** entities, const size_t num_entities) { - py::list entity_list; + nb::list entity_list; for (size_t i = 0; i < num_entities; ++i) { auto address = reinterpret_cast(entities[i]); if (address) { @@ -206,7 +208,7 @@ _get_ready_entities(const EntityArray ** entities, const size_t num_entities) return entity_list; } -py::list +nb::list WaitSet::get_ready_entities(const std::string & entity_type) { if ("subscription" == entity_type) { @@ -239,7 +241,7 @@ WaitSet::wait(int64_t timeout) // Could be a long wait, release the GIL { - py::gil_scoped_release gil_release; + nb::gil_scoped_release gil_release; ret = rcl_wait(rcl_wait_set_.get(), timeout); } @@ -248,11 +250,11 @@ WaitSet::wait(int64_t timeout) } } -void define_waitset(py::object module) +void define_waitset(nb::object module) { - py::class_>(module, "WaitSet") - .def(py::init()) - .def_property_readonly( + nb::class_(module, "WaitSet") + .def(nb::init()) + .def_prop_ro( "pointer", [](const WaitSet & waitset) { return reinterpret_cast(waitset.rcl_ptr()); }, diff --git a/rclpy/src/rclpy/wait_set.hpp b/rclpy/src/rclpy/wait_set.hpp index 03c957482..14195e243 100644 --- a/rclpy/src/rclpy/wait_set.hpp +++ b/rclpy/src/rclpy/wait_set.hpp @@ -15,7 +15,9 @@ #ifndef RCLPY__WAIT_SET_HPP_ #define RCLPY__WAIT_SET_HPP_ -#include +#include +#include +#include #include @@ -31,7 +33,7 @@ #include "subscription.hpp" #include "timer.hpp" -namespace py = pybind11; +namespace nb = nanobind; namespace rclpy { @@ -149,7 +151,7 @@ class WaitSet : public Destroyable, public std::enable_shared_from_this * \param[in] entity_type String defining the entity ["subscription, client, service"] * \return List of wait set entities pointers ready for take */ - py::list + nb::list get_ready_entities(const std::string & entity_type); /// Wait until timeout is reached or event happened @@ -177,8 +179,8 @@ class WaitSet : public Destroyable, public std::enable_shared_from_this std::shared_ptr rcl_wait_set_; }; -/// Define a pybind11 wrapper for an rclpy::WaitSet -void define_waitset(py::object module); +/// Define a nanobind wrapper for an rclpy::WaitSet +void define_waitset(nb::object module); } // namespace rclpy #endif // RCLPY__WAIT_SET_HPP_ diff --git a/rclpy/test/test_lifecycle.py b/rclpy/test/test_lifecycle.py index c41abd522..2fa067dcd 100644 --- a/rclpy/test/test_lifecycle.py +++ b/rclpy/test/test_lifecycle.py @@ -52,7 +52,7 @@ def test_lifecycle_node_init() -> None: assert not hasattr(node, '_service_get_available_states') assert not hasattr(node, '_service_get_available_transitions') assert not hasattr(node, '_service_get_transition_graph') - # Make sure also that the services were not created in the pybind11 plugin + # Make sure also that the services were not created in the nanobind plugin assert not node._state_machine.service_change_state assert not node._state_machine.service_get_state assert not node._state_machine.service_get_available_states diff --git a/rclpy/test/test_python_allocator.cpp b/rclpy/test/test_python_allocator.cpp index 4e4152fda..454297182 100644 --- a/rclpy/test/test_python_allocator.cpp +++ b/rclpy/test/test_python_allocator.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include @@ -20,10 +20,24 @@ #include "python_allocator.hpp" -namespace py = pybind11; +// nanobind does not provide an embedding API like pybind11's scoped_interpreter, +// so use the plain CPython one. +class ScopedInterpreter +{ +public: + ScopedInterpreter() + { + Py_Initialize(); + } + + ~ScopedInterpreter() + { + Py_Finalize(); + } +}; TEST(test_allocator, vector) { - py::scoped_interpreter guard{}; // Start a Python interpreter + ScopedInterpreter guard; // Start a Python interpreter std::vector> container(42); @@ -36,7 +50,7 @@ TEST(test_allocator, vector) { } TEST(test_allocator, equality) { - py::scoped_interpreter guard{}; // Start a Python interpreter + ScopedInterpreter guard; // Start a Python interpreter rclpy::PythonAllocator int_alloc; rclpy::PythonAllocator float_alloc; @@ -46,7 +60,7 @@ TEST(test_allocator, equality) { } TEST(test_allocator, make_1) { - py::scoped_interpreter guard{}; // Start a Python interpreter + ScopedInterpreter guard; // Start a Python interpreter rclpy::PythonAllocator int_alloc; @@ -58,7 +72,7 @@ TEST(test_allocator, make_1) { } TEST(test_allocator, copy_construct_make_1) { - py::scoped_interpreter guard{}; // Start a Python interpreter + ScopedInterpreter guard; // Start a Python interpreter rclpy::PythonAllocator float_alloc; rclpy::PythonAllocator int_alloc(float_alloc); From 43b61f061e48f2cd9f13fd021be539bf15170668 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sat, 1 Aug 2026 14:48:47 -0400 Subject: [PATCH 2/4] Use nanobind stub generation and std::optional Signed-off-by: Sebastian Castro --- rclpy/CMakeLists.txt | 15 + rclpy/rclpy/impl/_rclpy_nanobind.pyi | 1161 ++++++----------- rclpy/rclpy/impl/service_introspection.pyi | 14 +- rclpy/src/rclpy/_rclpy_nanobind.cpp | 13 +- rclpy/src/rclpy/action_client.cpp | 9 +- rclpy/src/rclpy/action_client.hpp | 4 +- rclpy/src/rclpy/action_server.cpp | 9 +- rclpy/src/rclpy/action_server.hpp | 4 +- rclpy/src/rclpy/client.cpp | 20 +- rclpy/src/rclpy/client.hpp | 8 +- .../rclpy/events_executor/events_executor.cpp | 10 +- rclpy/src/rclpy/node.cpp | 38 +- rclpy/src/rclpy/node.hpp | 6 +- rclpy/src/rclpy/publisher.cpp | 10 +- rclpy/src/rclpy/publisher.hpp | 4 +- rclpy/src/rclpy/serialization.cpp | 14 +- rclpy/src/rclpy/service.cpp | 19 +- rclpy/src/rclpy/service.hpp | 6 +- rclpy/src/rclpy/subscription.cpp | 18 +- rclpy/src/rclpy/subscription.hpp | 7 +- rclpy/src/rclpy/utils.cpp | 46 +- rclpy/src/rclpy/utils.hpp | 4 +- 22 files changed, 565 insertions(+), 874 deletions(-) diff --git a/rclpy/CMakeLists.txt b/rclpy/CMakeLists.txt index d2df75f30..7d1efffb6 100644 --- a/rclpy/CMakeLists.txt +++ b/rclpy/CMakeLists.txt @@ -138,6 +138,21 @@ target_link_libraries(_rclpy_nanobind PRIVATE ) configure_build_install_location(_rclpy_nanobind) +# Generate the type stubs for the extension module into the source tree, +# so that they stay committed alongside the bindings. +nanobind_add_stub(_rclpy_nanobind_stub + MODULE _rclpy_nanobind + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/rclpy/impl/_rclpy_nanobind.pyi" + PYTHON_PATH "${CMAKE_CURRENT_BINARY_DIR}/test_rclpy" + DEPENDS _rclpy_nanobind +) +nanobind_add_stub(_rclpy_nanobind_service_introspection_stub + MODULE _rclpy_nanobind.service_introspection + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/rclpy/impl/service_introspection.pyi" + PYTHON_PATH "${CMAKE_CURRENT_BINARY_DIR}/test_rclpy" + DEPENDS _rclpy_nanobind +) + if(NOT WIN32) ament_environment_hooks( "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}" diff --git a/rclpy/rclpy/impl/_rclpy_nanobind.pyi b/rclpy/rclpy/impl/_rclpy_nanobind.pyi index f287815b1..808d3ce8a 100644 --- a/rclpy/rclpy/impl/_rclpy_nanobind.pyi +++ b/rclpy/rclpy/impl/_rclpy_nanobind.pyi @@ -1,255 +1,174 @@ -# Copyright 2024 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 __future__ import annotations - -import builtins -from enum import IntEnum -from types import TracebackType -from typing import (Any, Callable, Coroutine, Final, Generic, Literal, Optional, overload, - Sequence, TypeAlias, TypedDict, TypeVar) - - -from action_msgs.msg import GoalInfo -from action_msgs.msg._goal_status_array import GoalStatusArray -from action_msgs.srv._cancel_goal import CancelGoal -from rclpy.clock import JumpHandle -from rclpy.context import Context as RCLPyContext -from rclpy.duration import Duration -from rclpy.impl import service_introspection as service_introspection -from rclpy.node import Node as RCLPyNode -from rclpy.parameter import Parameter -from rclpy.subscription import MessageInfo -from rclpy.subscription_content_filter_options import ContentFilterOptions -from rclpy.task import Future -from rclpy.task import Task -from rclpy.type_support import Action -from rclpy.type_support import Srv -from rclpy.type_support import FeedbackMessage -from rclpy.type_support import FeedbackT -from rclpy.type_support import ImplT -from rclpy.type_support import GetResultServiceRequest -from rclpy.type_support import GetResultServiceResponse -from rclpy.type_support import GoalT -from rclpy.type_support import MsgT -from rclpy.type_support import ResultT -from rclpy.type_support import SendGoalServiceRequest -from rclpy.type_support import SendGoalServiceResponse -from rclpy.type_support import SrvRequestT -from rclpy.type_support import SrvResponseT -from type_description_interfaces.srv import GetTypeDescription - -T = TypeVar('T') - -# All things are defined in same order as defined in _rclpy_nanobind.cpp +"""ROS 2 Python client library.""" +from collections.abc import Callable, Sequence +import enum +from typing import overload + +from . import service_introspection as service_introspection -class Destroyable: +class Destroyable: def __enter__(self) -> None: ... - def __exit__(self, exc_type: type[BaseException] | None, - exc_val: BaseException | None, exctb: TracebackType | None) -> None: ... + def __exit__(self, arg0: object | None, arg1: object | None, arg2: object | None) -> None: ... def destroy_when_not_in_use(self) -> None: - """Destroy the rcl object as soon as it's not actively being used.""" + """ + Forcefully destroy the rcl object as soon as it's not actively being used + """ + +class ClockType(enum.IntEnum): + UNINITIALIZED = 0 + + ROS_TIME = 1 + + SYSTEM_TIME = 2 + STEADY_TIME = 3 -class ClockType(IntEnum): - UNINITIALIZED = ... - ROS_TIME = ... - SYSTEM_TIME = ... - STEADY_TIME = ... +class GoalEvent(enum.IntEnum): + EXECUTE = 0 + CANCEL_GOAL = 1 -class GoalEvent(IntEnum): - EXECUTE = ... - CANCEL_GOAL = ... - SUCCEED = ... - ABORT = ... - CANCELED = ... + SUCCEED = 2 + ABORT = 3 -RCL_DEFAULT_DOMAIN_ID: Final[int] = ... -RMW_DURATION_INFINITE: Final[int] = ... -RMW_QOS_DEADLINE_BEST_AVAILABLE: Final[int] = ... -RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE: Final[int] = ... + CANCELED = 4 +RCL_DEFAULT_DOMAIN_ID: int = 18446744073709551615 -class ClockChange(IntEnum): - ROS_TIME_NO_CHANGE = ... - """ROS time is active and will continue to be active".""" - ROS_TIME_ACTIVATED = ... - """ROS time is being activated.""" - ROS_TIME_DEACTIVATED = ... - """ROS TIME is being deactivated, the clock will report system time after the jump.""" - SYSTEM_TIME_NO_CHANGE = ... - """ROS time is inactive and the clock will keep reporting system time.""" +RMW_DURATION_INFINITE: int = 9223372036854775807 +RMW_QOS_DEADLINE_BEST_AVAILABLE: int = 9223372036854775806 -class QoSCompatibility(IntEnum): - OK = ... - WARNING = ... - ERROR = ... +RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE: int = 9223372036854775806 +class ClockChange(enum.IntEnum): + ROS_TIME_NO_CHANGE = 1 + """ROS time is active and will continue to be active""" -class _rmw_qos_compatibility_type_e(IntEnum): - RMW_QOS_COMPATIBILITY_OK = ... - RMW_QOS_COMPATIBILITY_WARNING = ... - RMW_QOS_COMPATIBILITY_ERROR = ... + ROS_TIME_ACTIVATED = 2 + """ROS time is being activated""" + ROS_TIME_DEACTIVATED = 3 + """ + ROS TIME is being deactivated, the clock will report system time after the jump + """ -_rmw_qos_compatibility_type_t: TypeAlias = _rmw_qos_compatibility_type_e + SYSTEM_TIME_NO_CHANGE = 4 + """ROS time is inactive and the clock will keep reporting system time""" +class QoSCompatibility(enum.IntEnum): + OK = 0 + + WARNING = 1 + + ERROR = 2 class QoSCheckCompatibleResult: - """Result type for checking QoS compatibility with result.""" + """Result type for checking QoS compatibility with result""" def __init__(self) -> None: ... @property - def compatibility(self) -> _rmw_qos_compatibility_type_t: ... + def compatibility(self) -> QoSCompatibility: ... @property def reason(self) -> str: ... - class RCUtilsError(RuntimeError): - - def __init__(self, error_text: str) -> None: ... - + pass class RMWError(RuntimeError): - - def __init__(self, error_text: str) -> None: ... - + pass class RCLError(RuntimeError): - - def __init__(self, error_text: str) -> None: ... - + pass class RCLInvalidROSArgsError(RCLError): pass - class UnknownROSArgsError(RuntimeError): pass - class NodeNameNonExistentError(RCLError): pass - class UnsupportedEventTypeError(RCLError): pass - class TimerCancelledError(RCLError): pass - -class NotImplementedError(builtins.NotImplementedError): # noqa: A001 +class NotImplementedError(NotImplementedError): pass - class InvalidHandle(RuntimeError): pass - -# Service Introspection imported above - - -class Client(Destroyable, Generic[SrvRequestT, SrvResponseT]): - - def __init__(self, node: Node, srv_type: type[Srv[SrvRequestT, SrvResponseT]], - srv_name: str, pyqos_profile: rmw_qos_profile_t) -> None: ... +class Client(Destroyable): + def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... @property def service_name(self) -> str: - """Get the name of the service.""" + """Get the name of the service""" @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" - def send_request(self, pyrequest: SrvRequestT) -> int: - """Send a request.""" + def send_request(self, arg: object, /) -> int: + """Send a request""" def service_server_is_available(self) -> bool: - """Return true if the service server is available.""" + """Return true if the service server is available""" - def take_response( - self, pyresponse_type: type[SrvResponseT] - ) -> tuple[rmw_service_info_t, SrvResponseT] | tuple[None, None]: - """Take a received response from an earlier request.""" + def take_response(self, arg: object, /) -> tuple: + """Take a received response from an earlier request""" - def configure_introspection( - self, - clock: Clock, - pyqos_service_event_pub: rmw_qos_profile_t, - introspection_state: service_introspection.ServiceIntrospectionState - ) -> None: - """Configure whether introspection is enabled.""" + def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: + """Configure whether introspection is enabled""" def get_logger_name(self) -> str: """Get the name of the logger associated with the node of the client.""" - def set_on_new_response_callback(self, callback: Callable[[int], None]) -> None: - """Set the on new response callback function for the client.""" - - def clear_on_new_response_callback(self) -> None: - """Clear the on new response callback function for the client.""" + def set_on_new_response_callback(self, callback: Callable[[int], None]) -> None: ... + def clear_on_new_response_callback(self) -> None: ... class Context(Destroyable): - - def __init__(self, pyargs: list[str], domain_id: int) -> None: ... + def __init__(self, arg0: list, arg1: int, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def get_domain_id(self) -> int: - """Retrieve domain id from init_options of context.""" + """Retrieves domain id from init_options of context.""" def ok(self) -> bool: - """Status of the the client library.""" + """Status of the the client library""" def shutdown(self) -> None: - """Shutdown context.""" - + """Shutdown context""" class rcl_duration_t: - - def __init__(self, nanoseconds: int) -> None: ... + def __init__(self, arg: int, /) -> None: ... @property def nanoseconds(self) -> int: ... - -class Publisher(Destroyable, Generic[MsgT]): - - def __init__(self, arg0: Node, arg1: type[MsgT], arg2: str, arg3: rmw_qos_profile_t) -> None: - """Create _rclpy.Publisher.""" +class Publisher(Destroyable): + def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def get_logger_name(self) -> str: - """Get the name of the logger associated with the node of the publisher.""" + """Get the name of the logger associated with the node of the publisher""" def get_subscription_count(self) -> int: """Count subscribers from a publisher.""" @@ -257,77 +176,57 @@ class Publisher(Destroyable, Generic[MsgT]): def get_topic_name(self) -> str: """Retrieve the topic name from a Publisher.""" - def publish(self, arg0: MsgT) -> None: - """Publish a message.""" + def publish(self, arg: object, /) -> None: + """Publish a message""" - def publish_raw(self, arg0: bytes) -> None: + def publish_raw(self, arg: bytes, /) -> None: """Publish a serialized message.""" - def wait_for_all_acked(self, arg0: rcl_duration_t) -> bool: - """Wait until all published message data is acknowledged.""" - - -class Service(Destroyable, Generic[SrvRequestT, SrvResponseT]): + def wait_for_all_acked(self, arg: rcl_duration_t, /) -> bool: + """Wait until all published message data is acknowledged""" - def __init__(self, node: Node, pysrv_type: type[Srv[SrvRequestT, SrvResponseT]], - name: str, pyqos_profile: rmw_qos_profile_t) -> None: ... +class Service(Destroyable): + def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" @property def name(self) -> str: - """Get the name of the service.""" + """Get the name of the service""" @property - def qos(self) -> _rmw_qos_profile_dict: - """Get the qos profile of the service.""" + def qos(self) -> dict: + """Get the qos profile of the service""" - def service_send_response(self, pyresponse: SrvResponseT, header: rmw_request_id_t) -> None: - """Send a response.""" + def service_send_response(self, arg0: object, arg1: rmw_request_id_t, /) -> None: + """Send a response""" - def service_take_request( - self, - pyrequest_type: type[SrvRequestT] - ) -> tuple[SrvRequestT, rmw_service_info_t] | tuple[None, None]: - """Take a request from a given service.""" + def service_take_request(self, arg: object, /) -> tuple: + """Take a request from a given service""" - def configure_introspection( - self, clock: Clock, - pyqos_service_event_pub: rmw_qos_profile_t, - introspection_state: service_introspection.ServiceIntrospectionState - ) -> None: - """Configure whether introspection is enabled.""" + def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: + """Configure whether introspection is enabled""" def get_logger_name(self) -> str: """Get the name of the logger associated with the node of the service.""" - def set_on_new_request_callback(self, callback: Callable[[int], None]) -> None: - """Set the on new request callback function for the service.""" - - def clear_on_new_request_callback(self) -> None: - """Clear the on new request callback function for the service.""" + def set_on_new_request_callback(self, callback: Callable[[int], None]) -> None: ... + def clear_on_new_request_callback(self) -> None: ... class TypeDescriptionService(Destroyable): - - def __init__(self, handle: Node) -> None: ... + def __init__(self, arg: Node, /) -> None: ... @property - def impl(self) -> Service[GetTypeDescription.Request, GetTypeDescription.Response]: + def impl(self) -> Service: """Get the rcl service wrapper capsule.""" - def handle_request( - self, pyrequest: GetTypeDescription.Request, - pyresponse_type: type[GetTypeDescription.Response], - node: Node - ) -> GetTypeDescription.Response: - """Handle an incoming request by calling RCL implementation.""" - + def handle_request(self, arg0: object, arg1: object, arg2: Node, /) -> object: + """Handle an incoming request by calling RCL implementation""" class rmw_service_info_t: - @property def source_timestamp(self) -> int: ... @@ -337,177 +236,107 @@ class rmw_service_info_t: @property def request_id(self) -> rmw_request_id_t: ... - class rmw_request_id_t: - @property def sequence_number(self) -> int: ... - -def rclpy_qos_check_compatible(publisher_qos_profile: rmw_qos_profile_t, - subscription_qos_profile: rmw_qos_profile_t - ) -> QoSCheckCompatibleResult: +def rclpy_qos_check_compatible(arg0: rmw_qos_profile_t, arg1: rmw_qos_profile_t, /) -> QoSCheckCompatibleResult: """Check if two QoS profiles are compatible.""" - -class ActionClient(Generic[GoalT, ResultT, FeedbackT, ImplT], Destroyable): - - def __init__( - self, - node: Node, - pyaction_type: type[Action[GoalT, ResultT, FeedbackT, ImplT]], - action_name: str, - goal_service_qos: rmw_qos_profile_t, - result_service_qos: rmw_qos_profile_t, - cancel_service_qos: rmw_qos_profile_t, - feedback_service_qos: rmw_qos_profile_t, - status_topic_qos: rmw_qos_profile_t, - enable_feedback_msg_optimization: bool - ) -> None: ... +class ActionClient(Destroyable): + def __init__(self, node: Node, action_type: object, action_name: str, goal_service_qos_profile: rmw_qos_profile_t, result_service_qos_profile: rmw_qos_profile_t, cancel_service_qos_profile: rmw_qos_profile_t, feedback_sub_qos_profile: rmw_qos_profile_t, status_sub_qos_profile: rmw_qos_profile_t, enable_feedback_msg_optimization: bool = False) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" - def take_goal_response(self, pymsg_type: type[SendGoalServiceResponse] - ) -> tuple[int, SendGoalServiceResponse] | tuple[None, None]: + def take_goal_response(self, arg: object, /) -> tuple: """Take an action goal response.""" - def send_result_request(self, pyrequest: GetResultServiceRequest) -> int: + def send_result_request(self, arg: object, /) -> int: """Send an action result request.""" - def take_cancel_response(self, pymsg_type: type[CancelGoal.Response] - ) -> tuple[int, CancelGoal.Response] | tuple[None, None]: + def take_cancel_response(self, arg: object, /) -> tuple: """Take an action cancel response.""" - def take_feedback(self, pymsg_type: type[FeedbackMessage[FeedbackT]] - ) -> FeedbackMessage[FeedbackT] | None: + def take_feedback(self, arg: object, /) -> object: """Take a feedback message from a given action client.""" - def send_cancel_request(self, pyrequest: CancelGoal.Request) -> int: + def send_cancel_request(self, arg: object, /) -> int: """Send an action cancel request.""" - def send_goal_request(self, pyrequest: SendGoalServiceRequest[GoalT]) -> int: + def send_goal_request(self, arg: object, /) -> int: """Send an action goal request.""" - def take_result_response( - self, - pymsg_type: type[GetResultServiceResponse[ResultT]] - ) -> tuple[int, GetResultServiceResponse[ResultT]] | tuple[None, None]: + def take_result_response(self, arg: object, /) -> tuple: """Take an action result response.""" - def get_num_entities(self) -> tuple[int, int, int, int, int]: + def get_num_entities(self) -> tuple: """Get the number of wait set entities that make up an action entity.""" def is_action_server_available(self) -> bool: """Check if an action server is available for the given action client.""" - def add_to_waitset(self, wait_set: WaitSet) -> None: + def add_to_waitset(self, arg: WaitSet, /) -> None: """Add an action entity to a wait set.""" - def is_ready(self, wait_set: WaitSet) -> tuple[bool, bool, bool, bool, bool]: + def is_ready(self, arg: WaitSet, /) -> tuple: """Check if an action entity has any ready wait set entities.""" - def take_status(self, pymsg_type: type[GoalStatusArray]) -> GoalStatusArray | None: + def take_status(self, arg: object, /) -> object: """Take an action status response.""" - def configure_introspection( - self, - clock: Clock, - pyqos_service_event_pub: Optional[rmw_qos_profile_t], - introspection_state: service_introspection.ServiceIntrospectionState - ) -> None: - """Configure whether internal client introspection is enabled.""" + def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: + """Configure whether internal client introspection is enabled""" - def configure_feedback_subscription_filter_add_goal_id(self, goal_id: bytes) -> bool: + def configure_feedback_subscription_filter_add_goal_id(self, arg: bytes, /) -> bool: """Configure feedback subscription content filter to add a goal ID.""" - def configure_feedback_subscription_filter_remove_goal_id(self, goal_id: bytes) -> bool: + def configure_feedback_subscription_filter_remove_goal_id(self, arg: bytes, /) -> bool: """Configure feedback subscription content filter to remove a goal ID.""" class ActionGoalHandle(Destroyable): - - def __init__(self, action_server: ActionServer[Any, Any, Any, Any], - pygoal_info_msg: GoalInfo) -> None: - ... + def __init__(self, arg0: ActionServer, arg1: object, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" - def get_status(self) -> GoalEvent: + def get_status(self) -> int: """Get the status of a goal.""" - def update_goal_state(self, event: GoalEvent) -> None: + def update_goal_state(self, arg: GoalEvent, /) -> None: """Update a goal state.""" def is_active(self) -> bool: """Check if a goal is active.""" - -class ActionServer(Generic[GoalT, ResultT, FeedbackT, ImplT], Destroyable): - - def __init__( - self, - node: Node, - rclpy_clock: Clock, - pyaction_type: type[Action[GoalT, ResultT, FeedbackT, ImplT]], - action_name: str, - goal_service_qos: rmw_qos_profile_t, - result_service_qos: rmw_qos_profile_t, - cancel_service_qos: rmw_qos_profile_t, - feedback_topic_qos: rmw_qos_profile_t, - status_topic_qos: rmw_qos_profile_t, - result_timeout: float - ) -> None: ... +class ActionServer(Destroyable): + def __init__(self, arg0: Node, arg1: Clock, arg2: object, arg3: str, arg4: rmw_qos_profile_t, arg5: rmw_qos_profile_t, arg6: rmw_qos_profile_t, arg7: rmw_qos_profile_t, arg8: rmw_qos_profile_t, arg9: float, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" - def take_goal_request( - self, - pymsg_type: type[SendGoalServiceRequest[GoalT]] - ) -> tuple[rmw_request_id_t, SendGoalServiceRequest[GoalT]] | tuple[None, None]: + def take_goal_request(self, arg: object, /) -> tuple: """Take an action goal request.""" - def send_goal_response( - self, - header: rmw_request_id_t, - pyresponse: SendGoalServiceResponse - ) -> None: + def send_goal_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: """Send an action goal response.""" - def send_result_response( - self, - header: rmw_request_id_t, - pyresponse: GetResultServiceResponse[ResultT] - ) -> None: + def send_result_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: """Send an action result response.""" - def take_cancel_request( - self, - pymsg_type: type[CancelGoal.Request] - ) -> tuple[rmw_request_id_t, CancelGoal.Request] | tuple[None, None]: + def take_cancel_request(self, arg: object, /) -> tuple: """Take an action cancel request.""" - def take_result_request( - self, - pymsg_type: type[GetResultServiceRequest] - ) -> tuple[rmw_request_id_t, GetResultServiceRequest] | tuple[None, None]: + def take_result_request(self, arg: object, /) -> tuple: """Take an action result request.""" - def send_cancel_response( - self, - header: rmw_request_id_t, - pyresponse: CancelGoal.Response - ) -> None: + def send_cancel_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: """Send an action cancel response.""" - def publish_feedback( - self, - pymsg: FeedbackT - ) -> None: + def publish_feedback(self, arg: object, /) -> None: """Publish a feedback message from a given action server.""" def publish_status(self) -> None: @@ -516,66 +345,46 @@ class ActionServer(Generic[GoalT, ResultT, FeedbackT, ImplT], Destroyable): def notify_goal_done(self) -> None: """Notify goal is done.""" - def goal_exists(self, pygoal_info: GoalInfo) -> bool: + def goal_exists(self, arg: object, /) -> bool: """Check is a goal exists in the server.""" - def process_cancel_request( - self, - pycancel_request: CancelGoal.Request, - pycancel_response_type: type[CancelGoal.Response] - ) -> CancelGoal.Response: - """Process a cancel request.""" + def process_cancel_request(self, arg0: object, arg1: object, /) -> object: + """Process a cancel request""" - def expire_goals(self, max_num_goals: int) -> tuple[GoalInfo, ...]: + def expire_goals(self, arg: int, /) -> tuple: """Expired goals.""" - def get_num_entities(self) -> tuple[int, int, int, int, int]: + def get_num_entities(self) -> tuple: """Get the number of wait set entities that make up an action entity.""" - def is_ready(self, wait_set: WaitSet) -> tuple[bool, bool, bool, bool]: + def is_ready(self, arg: WaitSet, /) -> tuple: """Check if an action entity has any ready wait set entities.""" - def add_to_waitset(self, wait_set: WaitSet) -> None: + def add_to_waitset(self, arg: WaitSet, /) -> None: """Add an action entity to a wait set.""" - def configure_introspection( - self, - clock: Clock, - pyqos_service_pub: Optional[rmw_qos_profile_t], - introspection_state: service_introspection.ServiceIntrospectionState - ) -> None: - """Configure whether internal service introspection is enabled.""" - + def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: + """Configure whether internal service introspection is enabled""" -def rclpy_action_get_rmw_qos_profile(rmw_profile: str) -> _rmw_qos_profile_dict: +def rclpy_action_get_rmw_qos_profile(arg: str, /) -> dict: """Get an action RMW QoS profile.""" - class GuardCondition(Destroyable): - - def __init__(self, context: Context) -> None: ... + def __init__(self, arg: Context, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def trigger_guard_condition(self) -> None: - """Trigger a general purpose guard condition.""" - - -class _TimeInfoDict(TypedDict): - expected_call_time: int - actual_call_time: int - + """Trigger a general purpose guard condition""" class Timer(Destroyable): - - def __init__(self, clock: Clock, context: Context, period_nsec: int, - autostart: bool) -> None: ... + def __init__(self, arg0: Clock, arg1: Context, arg2: int, arg3: bool, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def reset_timer(self) -> None: """Reset a timer.""" @@ -586,10 +395,12 @@ class Timer(Destroyable): def call_timer(self) -> None: """Call a timer and starts counting again.""" - def call_timer_with_info(self) -> _TimeInfoDict: - """Call a timer and starts counting again, retrieves actual and expected call time.""" + def call_timer_with_info(self) -> object: + """ + Call a timer and starts counting again, retrieves actual and expected call time. + """ - def change_timer_period(self, period_nsec: int) -> None: + def change_timer_period(self, arg: int, /) -> None: """Set the period of a timer.""" def time_until_next_call(self) -> int | None: @@ -607,35 +418,24 @@ class Timer(Destroyable): def is_timer_canceled(self) -> bool: """Check if a timer is canceled.""" - def set_on_reset_callback(self, callback: Callable[[int], None]) -> None: - """Set the on reset callback function for the timer.""" - - def clear_on_reset_callback(self) -> None: - """Clear the on reset callback function for the timer.""" + def set_on_reset_callback(self, callback: Callable[[int], None]) -> None: ... + def clear_on_reset_callback(self) -> None: ... -class Subscription(Destroyable, Generic[MsgT]): - - def __init__(self, node: Node, pymsg_type: type[MsgT], topic: str, - pyqos_profile: rmw_qos_profile_t, - content_filter_options: Optional[ContentFilterOptions] = None) -> None: ... +class Subscription(Destroyable): + def __init__(self, node: Node, msg_type: object, topic: str, qos_profile: rmw_qos_profile_t | None, content_filter_options: object | None = None, acceptable_buffer_backends: str | None = None) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" - @overload - def take_message(self, pymsg_type: type[MsgT], raw: Literal[True]) -> tuple[bytes, MessageInfo] | None: ... - - @overload - def take_message(self, pymsg_type: type[MsgT], raw: Literal[False]) -> tuple[MsgT, MessageInfo] | None: ... - - @overload - def take_message(self, pymsg_type: type[MsgT], raw: bool) -> tuple[MsgT | bytes, MessageInfo] | None: - """Take a message and its metadata from a subscription.""" + def take_message(self, arg0: object, arg1: bool, /) -> object: + """Take a message and its metadata from a subscription""" def get_logger_name(self) -> str: - """Get the name of the logger associated with the node of the subscription.""" + """ + Get the name of the logger associated with the node of the subscription. + """ def get_topic_name(self) -> str: """Return the resolved topic name of a subscription.""" @@ -643,28 +443,28 @@ class Subscription(Destroyable, Generic[MsgT]): def get_publisher_count(self) -> int: """Count the publishers from a subscription.""" - def set_on_new_message_callback(self, callback: Callable[[int], None]) -> None: - """Set the on new message callback function for the subscription.""" + def set_on_new_message_callback(self, callback: Callable[[int], None]) -> None: ... - def clear_on_new_message_callback(self) -> None: - """Clear the on new message callback function for the subscription.""" + def clear_on_new_message_callback(self) -> None: ... def is_cft_supported(self) -> bool: - """Check if content filtering is supported for this subscription.""" + """Check if subscription instance supports content filtering.""" def is_cft_enabled(self) -> bool: """Check if content filtering is enabled for this subscription.""" - def set_content_filter(self, filter_expression: str, expression_parameters: list[str]) -> None: - """Set the filter expression and expression parameters for the subscription.""" - - def get_content_filter(self) -> ContentFilterOptions: - """Get the filter expression and expression parameters for the subscription.""" + def set_content_filter(self, arg0: str, arg1: Sequence[str], /) -> None: + """ + Set the filter expression and expression parameters for the subscription. + """ + def get_content_filter(self) -> object: + """ + Get the filter expression and expression parameters for the subscription. + """ class rcl_time_point_t: - - def __init__(self, nanoseconds: int, clock_type: int) -> None: ... + def __init__(self, arg0: int, arg1: int, /) -> None: ... @property def nanoseconds(self) -> int: ... @@ -672,233 +472,154 @@ class rcl_time_point_t: @property def clock_type(self) -> ClockType: ... - class Clock(Destroyable): + def __init__(self, arg: int, /) -> None: ... - def __init__(self, clock_type: int) -> None: ... + @property + def pointer(self) -> int: + """Get the address of the entity as an integer""" def get_now(self) -> rcl_time_point_t: - """Value of the clock.""" + """Current value of the clock""" def get_ros_time_override_is_enabled(self) -> bool: - """Return if a clock using ROS time has the ROS time override enabled.""" + """Returns if a clock using ROS time has the ROS time override enabled.""" - def set_ros_time_override_is_enabled(self, enabled: bool) -> None: + def set_ros_time_override_is_enabled(self, arg: bool, /) -> None: """Set if a clock using ROS time has the ROS time override enabled.""" - def set_ros_time_override(self, time_point: rcl_time_point_t) -> None: + def set_ros_time_override(self, arg: rcl_time_point_t, /) -> None: """Set the ROS time override for a clock using ROS time.""" - def add_clock_callback(self, pyjump_handle: JumpHandle, - on_clock_change: bool, min_forward: int, - min_backward: int) -> None: + def add_clock_callback(self, arg0: object, arg1: bool, arg2: int, arg3: int, /) -> None: """Add a time jump callback to a clock.""" - def remove_clock_callback(self, pyjump_handle: JumpHandle) -> None: + def remove_clock_callback(self, arg: object, /) -> None: """Remove a time jump callback from a clock.""" - -_IsReadyValues = Literal['subscription', 'client', 'service', 'timer', 'guard_condition', 'event'] -_GetReadyEntityValues = Literal['subscription', 'client', 'service', 'timer', 'guard_condition'] - - class WaitSet(Destroyable): - - def __init__(self, number_of_subscriptions: int, number_of_guard_conditions: int, - number_of_timers: int, number_of_clients: int, number_of_services: int, - number_of_events: int, context: Context) -> None: - """Construct a WaitSet.""" + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, arg6: Context, /) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def clear_entities(self) -> None: - """Clear all the pointers in the wait set.""" - - def add_service(self, service: Service[Any, Any]) -> int: - """Add a service to the wait set structure.""" + """Clear all the pointers in the wait set""" - def add_subscription(self, subscription: Subscription[Any]) -> int: - """Add a subscription to the wait set structure.""" + def add_service(self, arg: Service, /) -> int: + """Add a service to the wait set structure""" - def add_client(self, client: Client[Any, Any]) -> int: - """Add a client to the wait set structure.""" + def add_subscription(self, arg: Subscription, /) -> int: + """Add a subscription to the wait set structure""" - def add_guard_condition(self, guard_condition: GuardCondition) -> int: - """Add a guard condition to the wait set structure.""" + def add_client(self, arg: Client, /) -> int: + """Add a client to the wait set structure""" - def add_timer(self, timer: Timer) -> int: - """Add a timer to the wait set structure.""" + def add_guard_condition(self, arg: GuardCondition, /) -> int: + """Add a guard condition to the wait set structure""" - def add_event(self, event: EventHandle[Any]) -> int: - """Add an event to the wait set structure.""" + def add_timer(self, arg: Timer, /) -> int: + """Add a timer to the wait set structure""" - def is_ready(self, entity_type: _IsReadyValues, index: int) -> bool: - """Check if an entity in the wait set is ready by its index.""" + def add_event(self, arg: EventHandle, /) -> int: + """Add an event to the wait set structure""" - def get_ready_entities(self, entity_type: _GetReadyEntityValues) -> list[int]: - """Get list of entities ready by entity type.""" + def is_ready(self, arg0: str, arg1: int, /) -> bool: + """Check if an entity in the wait set is ready by its index""" - def wait(self, timeout: int) -> None: - """Wait until timeout is reached or event happened.""" + def get_ready_entities(self, arg: str, /) -> list: + """Get list of entities ready by entity type""" + def wait(self, arg: int, /) -> None: + """Wait until timeout is reached or event happened""" -def rclpy_expand_topic_name(topic: str, node_name: str, node_namespace: str) -> str: +def rclpy_expand_topic_name(arg0: str, arg1: str, arg2: str, /) -> str: """Expand a topic name.""" - -def rclpy_remap_topic_name(node: Node, topic_name: str) -> str: +def rclpy_remap_topic_name(arg0: Node, arg1: str, /) -> str: """Remap a topic name.""" +def rclpy_get_validation_error_for_topic_name(arg: str, /) -> object: + """ + Get the error message and invalid index of a topic name or None if valid. + """ -def rclpy_get_validation_error_for_topic_name(topic_name: str) -> tuple[str, int] | None: - """Get the error message and invalid index of a topic name or None if valid.""" - - -def rclpy_get_validation_error_for_full_topic_name(topic_name: str) -> tuple[str, int] | None: - """Get the error message and invalid index of a full topic name or None if valid.""" - +def rclpy_get_validation_error_for_full_topic_name(arg: str, /) -> object: + """ + Get the error message and invalid index of a full topic name or None if valid. + """ -def rclpy_get_validation_error_for_namespace(namespace_: str) -> tuple[str, int] | None: - """Get the error message and invalid index of a namespace or None if valid.""" +def rclpy_get_validation_error_for_namespace(arg: str, /) -> object: + """ + Get the error message and invalid index of a namespace or None if valid. + """ +def rclpy_get_validation_error_for_node_name(arg: str, /) -> object: + """ + Get the error message and invalid index of a node name or None if valid. + """ -def rclpy_get_validation_error_for_node_name(namespace_: str) -> tuple[str, int] | None: - """Get the error message and invalid index of a node name or None if valid.""" - - -def rclpy_resolve_name(node: Node, topic_name: str, only_expand: bool, is_service: bool) -> str: +def rclpy_resolve_name(arg0: Node, arg1: str, arg2: bool, arg3: bool, /) -> str: """Expand and remap a topic or service name.""" - -def rclpy_get_topic_names_and_types(node: Node, no_demangle: bool) -> list[tuple[str, list[str]]]: +def rclpy_get_topic_names_and_types(arg0: Node, arg1: bool, /) -> list: """Get all topic names and types in the ROS graph.""" - -def rclpy_get_publisher_names_and_types_by_node(node: Node, no_demangle: bool, node_name: str, - node_namespace: str - ) -> list[tuple[str, list[str]]]: +def rclpy_get_publisher_names_and_types_by_node(arg0: Node, arg1: bool, arg2: str, arg3: str, /) -> list: """Get topic names and types for which a remote node has publishers.""" - -def rclpy_get_subscriber_names_and_types_by_node(node: Node, no_demangle: bool, node_name: str, - node_namespace: str - ) -> list[tuple[str, list[str]]]: +def rclpy_get_subscriber_names_and_types_by_node(arg0: Node, arg1: bool, arg2: str, arg3: str, /) -> list: """Get topic names and types for which a remote node has subscribers.""" - -class _TypeHashDict(TypedDict): - version: int - value: bytes - - -class _TopicEndpointInfoDict(TypedDict): - node_name: str - node_namespace: str - topic_type: str - topic_type_hash: _TypeHashDict - endpoint_type: int - endpoint_gid: list[int] - qos_profile: _rmw_qos_profile_dict - - -class _ServiceEndpointInfoDict(TypedDict): - node_name: str - node_namespace: str - service_type: str - service_type_hash: _TypeHashDict - qos_profiles: list[_rmw_qos_profile_dict] - endpoint_gids: list[list[int]] - endpoint_type: int - endpoint_count: int - - -class _ActionEndpointInfoDict(TypedDict): - goal_service_info: Optional[_ServiceEndpointInfoDict] - cancel_service_info: Optional[_ServiceEndpointInfoDict] - result_service_info: Optional[_ServiceEndpointInfoDict] - feedback_topic_info: Optional[_TopicEndpointInfoDict] - status_topic_info: Optional[_TopicEndpointInfoDict] - - -def rclpy_get_publishers_info_by_topic(node: Node, topic_name: str, no_mangle: bool - ) -> list[_TopicEndpointInfoDict]: +def rclpy_get_publishers_info_by_topic(arg0: Node, arg1: str, arg2: bool, /) -> list: """Get publishers info for a topic.""" - -def rclpy_get_subscriptions_info_by_topic(node: Node, topic_name: str, no_mangle: bool - ) -> list[_TopicEndpointInfoDict]: +def rclpy_get_subscriptions_info_by_topic(arg0: Node, arg1: str, arg2: bool, /) -> list: """Get subscriptions info for a topic.""" - -def rclpy_get_clients_info_by_service(node: Node, service_name: str, no_mangle: bool - ) -> list[_ServiceEndpointInfoDict]: +def rclpy_get_clients_info_by_service(arg0: Node, arg1: str, arg2: bool, /) -> list: """Get clients info for a service.""" - -def rclpy_get_servers_info_by_service(node: Node, service_name: str, no_mangle: bool - ) -> list[_ServiceEndpointInfoDict]: +def rclpy_get_servers_info_by_service(arg0: Node, arg1: str, arg2: bool, /) -> list: """Get servers info for a service.""" - -def rclpy_get_service_names_and_types(node: Node) -> list[tuple[str, list[str]]]: - """Get all service names and types in the ROS graph.""" - - -def rclpy_get_service_names_and_types_by_node(node: Node, node_name: str, node_namespace: str - ) -> list[tuple[str, list[str]]]: +def rclpy_get_service_names_and_types(arg: Node, /) -> list: """Get all service names and types in the ROS graph.""" - -def rclpy_get_client_names_and_types_by_node(node: Node, node_name: str, node_namespace: str - ) -> list[tuple[str, list[str]]]: +def rclpy_get_service_names_and_types_by_node(arg0: Node, arg1: str, arg2: str, /) -> list: """Get service names and types for which a remote node has servers.""" +def rclpy_get_client_names_and_types_by_node(arg0: Node, arg1: str, arg2: str, /) -> list: + """Get service names and types for which a remote node has clients.""" -def rclpy_get_action_client_names_and_types_by_node(node: Node, node_name: str, - node_namespace: str - ) -> list[tuple[str, list[str]]]: +def rclpy_get_action_client_names_and_types_by_node(arg0: Node, arg1: str, arg2: str, /) -> list: """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]]]: +def rclpy_get_action_server_names_and_types_by_node(arg0: Node, arg1: str, arg2: str, /) -> list: """Get action server names and types by node.""" - -def rclpy_get_action_names_and_types(node: Node) -> list[tuple[str, list[str]]]: +def rclpy_get_action_names_and_types(arg: Node, /) -> list: """Get all action names and types in the ROS graph.""" - -def rclpy_get_action_clients_info_by_action(node: Node, action_name: str - ) -> list[_ActionEndpointInfoDict]: +def rclpy_get_action_clients_info_by_action(arg0: Node, arg1: str, /) -> list: """Get action clients info for an action.""" - -def rclpy_get_action_servers_info_by_action(node: Node, action_name: str - ) -> list[_ActionEndpointInfoDict]: +def rclpy_get_action_servers_info_by_action(arg0: Node, arg1: str, /) -> list: """Get action servers info for an action.""" - -def rclpy_serialize(pymsg: MsgT, py_msg_type: type[MsgT]) -> bytes: +def rclpy_serialize(arg0: object, arg1: object, /) -> bytes: """Serialize a ROS message.""" - -def rclpy_deserialize(pybuffer: bytes, pymsg_type: type[MsgT]) -> MsgT: +def rclpy_deserialize(arg0: bytes, arg1: object, /) -> object: """Deserialize a ROS message.""" - class Node(Destroyable): - - def __init__(self, node_name: str, namespace_: str, context: Context, - pycli_args: list[str] | None, use_global_arguments: bool, - enable: bool, rosout_qos_profile: rmw_qos_profile_t) -> None: ... + def __init__(self, arg0: str, arg1: str, arg2: Context, arg3: list | None, arg4: bool, arg5: bool, arg6: rmw_qos_profile_t | None) -> None: ... @property def pointer(self) -> int: - """Get the address of the entity as an integer.""" + """Get the address of the entity as an integer""" def get_fully_qualified_name(self) -> str: """Get the fully qualified name of the node.""" @@ -912,99 +633,96 @@ class Node(Destroyable): def get_namespace(self) -> str: """Get the namespace of a node.""" - def get_count_publishers(self, topic_name: str) -> int: - """Return the count of all the publishers known for that topic in the entire ROS graph.""" - - def get_count_subscribers(self, topic_name: str) -> int: - """Return the count of all the subscribers known for that topic in the entire ROS graph.""" - - def get_count_clients(self, service_name: str) -> int: - """Return the count of all the clients known for that service in the entire ROS graph.""" - - def get_count_services(self, service_name: str) -> int: - """Return the count of all the servers known for that service in the entire ROS graph.""" - - def get_count_action_clients(self, action_name: str) -> int: - """Return the count of the action clients known for that action in the entire ROS graph.""" - - def get_count_action_servers(self, action_name: str) -> int: - """Return the count of the action servers known for that action in the entire ROS graph.""" - - def get_node_names_and_namespaces(self) -> list[tuple[str, str]]: - """Get the list of nodes discovered by the provided node.""" - - def get_node_names_and_namespaces_with_enclaves(self) -> list[tuple[str, str, str]]: - """Get the list of nodes discovered by the provided node, with their enclaves.""" - - def get_action_client_names_and_types_by_node(self, remote_node_name: str, - remote_node_namespace: str) -> list[tuple[str, - list[str]]]: + def get_count_publishers(self, arg: str, /) -> int: + """ + Returns the count of all the publishers known for that topic in the entire ROS graph. + """ + + def get_count_subscribers(self, arg: str, /) -> int: + """ + Returns the count of all the subscribers known for that topic in the entire ROS graph. + """ + + def get_count_clients(self, arg: str, /) -> int: + """ + Returns the count of all the clients known for that service in the entire ROS graph. + """ + + def get_count_services(self, arg: str, /) -> int: + """ + Returns the count of all the servers known for that service in the entire ROS graph. + """ + + def get_count_action_clients(self, arg: str, /) -> int: + """ + Returns the count of all the action clients known for that action in the entire ROS graph. + """ + + def get_count_action_servers(self, arg: str, /) -> int: + """ + Returns the count of all the action servers known for that action in the entire ROS graph. + """ + + def get_node_names_and_namespaces(self) -> list: + """Get the list of nodes discovered by the provided node""" + + def get_node_names_and_namespaces_with_enclaves(self) -> list: + """ + Get the list of nodes discovered by the provided node, with their respective enclaves. + """ + + def get_action_client_names_and_types_by_node(self, arg0: str, arg1: str, /) -> list: """Get action client names and types by node.""" - def get_action_server_names_and_types_by_node(self, remote_node_name: str, - remote_node_namespace: str) -> list[tuple[str, - list[str]]]: + def get_action_server_names_and_types_by_node(self, arg0: str, arg1: str, /) -> list: """Get action server names and types by node.""" - def get_action_names_and_types(self) -> list[tuple[str, list[str]]]: + def get_action_names_and_types(self) -> list: """Get action names and types.""" - def get_parameters(self, pyparamter_cls: type[Parameter[Any]]) -> dict[str, Parameter[Any]]: - """Get a list of parameters for the current node.""" + def get_parameters(self, arg: object, /) -> dict: + """Get a list of parameters for the current node""" +class EventHandle(Destroyable): + @overload + def __init__(self, arg0: Subscription, arg1: rcl_subscription_event_type_t, /) -> None: ... -class _rmw_qos_incompatible_event_status_s: - total_count: int - total_count_change: int - last_policy_kind: rmw_qos_policy_kind_t + @overload + def __init__(self, arg0: Publisher, arg1: rcl_publisher_event_type_t, /) -> None: ... + @property + def pointer(self) -> int: + """Get the address of the entity as an integer""" -_rmw_qos_incompatible_event_status_t: TypeAlias = _rmw_qos_incompatible_event_status_s -_rmw_offered_qos_incompatible_event_status_t: TypeAlias = _rmw_qos_incompatible_event_status_t + def take_event(self) -> object: + """Get pending data from a ready event""" +class rcl_subscription_event_type_t(enum.IntEnum): + RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED = 0 -class EventHandle(Destroyable, Generic[T]): + RCL_SUBSCRIPTION_LIVELINESS_CHANGED = 1 - @overload - def __init__( - self, - subscription: Subscription[Any], - event_type: rcl_subscription_event_type_t - ) -> None: ... + RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS = 2 - @overload - def __init__( - self, - publisher: Publisher[Any], - event_type: rcl_publisher_event_type_t - ) -> None: ... + RCL_SUBSCRIPTION_MESSAGE_LOST = 3 - @property - def pointer(self) -> int: - """Get the address of the entity as an integer.""" + RCL_SUBSCRIPTION_INCOMPATIBLE_TYPE = 4 - def take_event(self) -> T | None: - """Get pending data from a ready event.""" + RCL_SUBSCRIPTION_MATCHED = 5 +class rcl_publisher_event_type_t(enum.IntEnum): + RCL_PUBLISHER_OFFERED_DEADLINE_MISSED = 0 -class rcl_subscription_event_type_t(IntEnum): - RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED = ... - RCL_SUBSCRIPTION_LIVELINESS_CHANGED = ... - RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS = ... - RCL_SUBSCRIPTION_MESSAGE_LOST = ... - RCL_SUBSCRIPTION_INCOMPATIBLE_TYPE = ... - RCL_SUBSCRIPTION_MATCHED = ... + RCL_PUBLISHER_LIVELINESS_LOST = 1 + RCL_PUBLISHER_OFFERED_INCOMPATIBLE_QOS = 2 -class rcl_publisher_event_type_t(IntEnum): - RCL_PUBLISHER_OFFERED_DEADLINE_MISSED = ... - RCL_PUBLISHER_LIVELINESS_LOST = ... - RCL_PUBLISHER_OFFERED_INCOMPATIBLE_QOS = ... - RCL_PUBLISHER_INCOMPATIBLE_TYPE = ... - RCL_PUBLISHER_MATCHED = ... + RCL_PUBLISHER_INCOMPATIBLE_TYPE = 3 + RCL_PUBLISHER_MATCHED = 4 class rmw_requested_deadline_missed_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1012,8 +730,8 @@ class rmw_requested_deadline_missed_status_t: @property def total_count_change(self) -> int: ... - class rmw_liveliness_changed_status_t: + def __init__(self) -> None: ... @property def alive_count(self) -> int: ... @@ -1027,8 +745,8 @@ class rmw_liveliness_changed_status_t: @property def not_alive_count_change(self) -> int: ... - class rmw_message_lost_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1036,8 +754,8 @@ class rmw_message_lost_status_t: @property def total_count_change(self) -> int: ... - class rmw_requested_qos_incompatible_event_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1048,8 +766,8 @@ class rmw_requested_qos_incompatible_event_status_t: @property def last_policy_kind(self) -> rmw_qos_policy_kind_t: ... - class rmw_offered_deadline_missed_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1057,8 +775,8 @@ class rmw_offered_deadline_missed_status_t: @property def total_count_change(self) -> int: ... - class rmw_liveliness_lost_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1066,8 +784,8 @@ class rmw_liveliness_lost_status_t: @property def total_count_change(self) -> int: ... - class rmw_matched_status_t: + def __init__(self) -> None: ... @property def total_count(self) -> int: ... @@ -1081,202 +799,163 @@ class rmw_matched_status_t: @property def current_count_change(self) -> int: ... +class rmw_qos_policy_kind_t(enum.IntEnum): + RMW_QOS_POLICY_INVALID = 1 -class rmw_qos_policy_kind_t(IntEnum): - RMW_QOS_POLICY_INVALID = ... - RMW_QOS_POLICY_DURABILITY = ... - RMW_QOS_POLICY_DEADLINE = ... - RMW_QOS_POLICY_LIVELINESS = ... - RMW_QOS_POLICY_RELIABILITY = ... - RMW_QOS_POLICY_HISTORY = ... - RMW_QOS_POLICY_LIFESPAN = ... - RMW_QOS_POLICY_DEPTH = ... - RMW_QOS_POLICY_LIVELINESS_LEASE_DURATION = ... - RMW_QOS_POLICY_AVOID_ROS_NAMESPACE_CONVENTIONS = ... + RMW_QOS_POLICY_DURABILITY = 2 + RMW_QOS_POLICY_DEADLINE = 4 -class rmw_incompatible_type_status_t: + RMW_QOS_POLICY_LIVELINESS = 8 - @property - def total_count_change(self) -> int: ... + RMW_QOS_POLICY_RELIABILITY = 16 + RMW_QOS_POLICY_HISTORY = 32 -def rclpy_get_rmw_implementation_identifier() -> str: - """Retrieve the identifier for the active RMW implementation.""" + RMW_QOS_POLICY_LIFESPAN = 64 + RMW_QOS_POLICY_DEPTH = 128 -def rclpy_assert_liveliness(publisher: Publisher[Any]) -> None: - """Assert the liveliness of an entity.""" + RMW_QOS_POLICY_LIVELINESS_LEASE_DURATION = 256 + RMW_QOS_POLICY_AVOID_ROS_NAMESPACE_CONVENTIONS = 512 -def rclpy_remove_ros_args(pycli_args: Sequence[str]) -> list[str]: - """Remove ROS-specific arguments from argument vector.""" +class rmw_incompatible_type_status_t: + def __init__(self) -> None: ... + + @property + def total_count_change(self) -> int: ... +def publisher_event_type_is_supported(arg: rcl_publisher_event_type_t, /) -> bool: + """ + Check if a publisher event type is supported by the active RMW implementation. + """ -_PredefinedQosProfileTNames = Literal['qos_profile_sensor_data', 'qos_profile_default', - 'qos_profile_system_default', 'qos_profile_services_default', - 'qos_profile_unknown', 'qos_profile_parameters', - 'qos_profile_parameter_events', 'qos_profile_best_available', - 'qos_profile_rosout_default'] +def subscription_event_type_is_supported(arg: rcl_subscription_event_type_t, /) -> bool: + """ + Check if a subscription event type is supported by the active RMW implementation. + """ +def rclpy_get_rmw_implementation_identifier() -> str: + """Retrieve the identifier for the active RMW implementation.""" -class _rmw_qos_profile_dict(TypedDict): - depth: int - history: int - reliability: int - durability: int - lifespan: Duration - deadline: Duration - liveliness: int - liveliness_lease_duration: Duration - avoid_ros_namespace_conventions: bool +def rclpy_assert_liveliness(arg: Publisher, /) -> None: + """Assert the liveliness of an entity.""" +def rclpy_remove_ros_args(arg: list | None) -> list: + """Remove ROS-specific arguments from argument vector.""" class rmw_qos_profile_t: + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: rcl_duration_t, arg5: rcl_duration_t, arg6: int, arg7: rcl_duration_t, arg8: bool, /) -> None: ... - def __init__( - self, - qos_history: int, - qos_depth: int, - qos_reliability: int, - qos_durability: int, - pyqos_lifespan: rcl_duration_t, - pyqos_deadline: rcl_duration_t, - qos_liveliness: int, - pyqos_liveliness_lease_duration: rcl_duration_t, - avoid_ros_namespace_conventions: bool - ) -> None: ... - - def to_dict(self) -> _rmw_qos_profile_dict: ... + def to_dict(self) -> dict: ... @staticmethod - def predefined(qos_profile_name: _PredefinedQosProfileTNames) -> rmw_qos_profile_t: ... - + def predefined(arg: str, /) -> rmw_qos_profile_t: ... def rclpy_logging_fini() -> None: """Finalize RCL logging.""" - -def rclpy_logging_configure(context: Context) -> None: +def rclpy_logging_configure(arg: Context, /) -> None: """Initialize RCL logging.""" +class RCUTILS_LOG_SEVERITY(enum.IntEnum): + RCUTILS_LOG_SEVERITY_UNSET = 0 -class RCUTILS_LOG_SEVERITY(IntEnum): - RCUTILS_LOG_SEVERITY_UNSET = ... - RCUTILS_LOG_SEVERITY_DEBUG = ... - RCUTILS_LOG_SEVERITY_INFO = ... - RCUTILS_LOG_SEVERITY_WARN = ... - RCUTILS_LOG_SEVERITY_ERROR = ... - RCUTILS_LOG_SEVERITY_FATAL = ... - + RCUTILS_LOG_SEVERITY_DEBUG = 10 -def rclpy_logging_get_separator_string() -> str: ... + RCUTILS_LOG_SEVERITY_INFO = 20 + RCUTILS_LOG_SEVERITY_WARN = 30 -def rclpy_logging_initialize() -> None: ... + RCUTILS_LOG_SEVERITY_ERROR = 40 + RCUTILS_LOG_SEVERITY_FATAL = 50 -def rclpy_logging_shutdown() -> None: ... - - -def rclpy_logging_set_logger_level(name: str, level: int, - detailed_error: bool = False) -> None: ... - - -def rclpy_logging_get_logger_effective_level(name: str) -> int: ... +def rclpy_logging_get_separator_string() -> str: ... +def rclpy_logging_initialize() -> None: ... -def rclpy_logging_logger_is_enabled_for(name: str, severity: int) -> bool: ... +def rclpy_logging_shutdown() -> None: ... +def rclpy_logging_set_logger_level(name: str, level: int, detailed_error: bool = False) -> None: ... -def rclpy_logging_rcutils_log(severity: int, name: str, message: str, function_name: str, - file_name: str, line_number: int) -> None: ... +def rclpy_logging_get_logger_effective_level(arg: str, /) -> int: ... +def rclpy_logging_logger_is_enabled_for(arg0: str, arg1: int, /) -> bool: ... -def rclpy_logging_severity_level_from_string(log_level: str) -> int: ... +def rclpy_logging_rcutils_log(arg0: int, arg1: str, arg2: str, arg3: str, arg4: str, arg5: int, /) -> None: ... +def rclpy_logging_severity_level_from_string(arg: str, /) -> int: ... def rclpy_logging_get_logging_directory() -> str: ... +def rclpy_logging_rosout_add_sublogger(arg0: str, arg1: str, /) -> bool: ... -def rclpy_logging_rosout_add_sublogger(logger_name: str, sublogger_name: str) -> bool: ... - - -def rclpy_logging_rosout_remove_sublogger(logger_name: str, sublogger_name: str) -> None: ... - - -def rclpy_logging_get_logger_level(name: str) -> int: ... +def rclpy_logging_rosout_remove_sublogger(arg0: str, arg1: str, /) -> None: ... +def rclpy_logging_get_logger_level(arg: str, /) -> int: ... -def register_sigint_guard_condition(guard_condition: GuardCondition) -> None: +def register_sigint_guard_condition(arg: GuardCondition, /) -> None: """Register a guard condition to be called on SIGINT.""" - -def unregister_sigint_guard_condition(guard_condition: GuardCondition) -> None: +def unregister_sigint_guard_condition(arg: GuardCondition, /) -> None: """Stop triggering a guard condition when SIGINT occurs.""" - -def install_signal_handlers(options: SignalHandlerOptions) -> None: +def install_signal_handlers(arg: SignalHandlerOptions, /) -> None: """Install rclpy signal handlers.""" - def get_current_signal_handlers_options() -> SignalHandlerOptions: """Get currently installed signal handler options.""" - def uninstall_signal_handlers() -> None: """Uninstall rclpy signal handlers.""" - -class SignalHandlerOptions(IntEnum): +class SignalHandlerOptions(enum.IntEnum): """Enum with values: `ALL`, `SIGINT`, `SIGTERM`, `NO`.""" - NO = ... - SigInt = ... - SigTerm = ... - ALL = ... + ALL = 3 + NO = 0 -class ClockEvent: + SIGINT = 1 + SIGTERM = 2 + +class ClockEvent: def __init__(self) -> None: ... - def wait_until_steady(self, clock: Clock, until: rcl_time_point_t) -> None: - """Wait for the event to be set (monotonic wait).""" + def wait_until_steady(self, arg0: Clock, arg1: rcl_time_point_t, /) -> None: + """Wait for the event to be set (monotonic wait)""" - def wait_until_system(self, clock: Clock, until: rcl_time_point_t) -> None: - """Wait for the event to be set (system timed wait).""" + def wait_until_system(self, arg0: Clock, arg1: rcl_time_point_t, /) -> None: + """Wait for the event to be set (system timed wait)""" - def wait_until_ros(self, clock: Clock, until: rcl_time_point_t) -> None: - """Wait for the event to be set (ROS timed wait).""" + def wait_until_ros(self, arg0: Clock, arg1: rcl_time_point_t, /) -> None: + """Wait for the event to be set (ROS timed wait)""" def is_set(self) -> bool: """Return True if the event is set, False otherwise.""" - def set(self) -> None: # noqa: A003 + def set(self) -> None: """Set the event, waking all those who wait on it.""" def clear(self) -> None: """Unset the event.""" - -_LifecycleStateMachineState: TypeAlias = tuple[int, str] - - class LifecycleStateMachine(Destroyable): - - def __init__(self, node: Node, clock: Clock, enable_com_interface: bool) -> None: ... + def __init__(self, arg0: Node, arg1: Clock, arg2: bool, /) -> None: ... @property def initialized(self) -> bool: """Check if state machine is initialized.""" @property - def current_state(self) -> _LifecycleStateMachineState: + def current_state(self) -> tuple: """Get the current state machine state.""" @property - def available_states(self) -> list[_LifecycleStateMachineState]: + def available_states(self) -> list[tuple[int, str]]: """Get the available states.""" @property @@ -1287,82 +966,76 @@ class LifecycleStateMachine(Destroyable): def transition_graph(self) -> list[tuple[int, str, int, str, int, str]]: """Get the transition graph.""" - def get_transition_by_label(self, label: str) -> int: + def get_transition_by_label(self, arg: str, /) -> int: """Get the transition id from a transition label.""" - def trigger_transition_by_id(self, transition_id: int, publish_update: bool) -> None: + def trigger_transition_by_id(self, arg0: int, arg1: bool, /) -> None: """Trigger a transition by transition id.""" - def trigger_transition_by_label(self, label: str, publish_update: bool) -> None: + def trigger_transition_by_label(self, arg0: str, arg1: bool, /) -> None: """Trigger a transition by label.""" @property - def service_change_state(self) -> Service[Any, Any]: + def service_change_state(self) -> Service: """Get the change state service.""" @property - def service_get_state(self) -> Service[Any, Any]: + def service_get_state(self) -> Service: """Get the get state service.""" @property - def service_get_available_states(self) -> Service[Any, Any]: + def service_get_available_states(self) -> Service: """Get the get available states service.""" @property - def service_get_available_transitions(self) -> Service[Any, Any]: + def service_get_available_transitions(self) -> Service: """Get the get available transitions service.""" @property - def service_get_transition_graph(self) -> Service[Any, Any]: + def service_get_transition_graph(self) -> Service: """Get the get transition graph service.""" +class TransitionCallbackReturnType(enum.IntEnum): + SUCCESS = 97 + """Callback succeeded.""" -class TransitionCallbackReturnType(IntEnum): - SUCCESS = ... - FAILURE = ... - ERROR = ... + FAILURE = 98 + """Callback failed.""" - def to_label(self) -> str: - """Convert the transition callback return code to a transition label.""" + ERROR = 99 + """Callback had an error.""" + def to_label(self) -> str: + """Convert the transition callback return code to a transition label""" class EventsExecutor: - - def __init__(self, context: RCLPyContext): ... + def __init__(self, context: object) -> None: ... @property - def context(self) -> RCLPyContext: ... + def context(self) -> object: ... - @overload - def create_task(self, callback: Callable[..., Coroutine[Any, Any, T]], - *args: Any, **kwargs: Any - ) -> Task[T]: ... + def create_task(self, callback: object, *args, **kwargs) -> object: ... - @overload - def create_task(self, callback: Callable[..., T], *args: Any, **kwargs: Any - ) -> Task[T]: ... + def create_future(self) -> object: ... - def shutdown(self, timeout_sec: Optional[float] = None) -> bool: ... + def shutdown(self, timeout_sec: float | None = None) -> bool: ... - def add_node(self, node: RCLPyNode) -> bool: ... + def add_node(self, node: object) -> bool: ... - def remove_node(self, node: RCLPyNode) -> None: ... + def remove_node(self, node: object) -> None: ... def wake(self) -> None: ... - def get_nodes(self) -> list[RCLPyNode]: ... + def get_nodes(self) -> list: ... def spin(self) -> None: ... - def spin_once(self, timeout_sec: Optional[float] = None) -> None: ... + def spin_once(self, timeout_sec: float | None = None) -> None: ... - def spin_until_future_complete(self, future: Future[Any], - timeout_sec: Optional[float] = None) -> None: ... + def spin_until_future_complete(self, future: object, timeout_sec: float | None = None) -> None: ... - def spin_once_until_future_complete(self, future: Future[Any], - timeout_sec: Optional[float] = None) -> None: ... + def spin_once_until_future_complete(self, future: object, timeout_sec: float | None = None) -> None: ... def __enter__(self) -> EventsExecutor: ... - def __exit__(self, exc_type: type[BaseException] | None, - exc_val: BaseException | None, exctb: TracebackType | None) -> None: ... + def __exit__(self, arg0: object | None, arg1: object | None, arg2: object | None) -> None: ... diff --git a/rclpy/rclpy/impl/service_introspection.pyi b/rclpy/rclpy/impl/service_introspection.pyi index 2f68be303..a7c5bcdea 100644 --- a/rclpy/rclpy/impl/service_introspection.pyi +++ b/rclpy/rclpy/impl/service_introspection.pyi @@ -1,7 +1,11 @@ -from enum import IntEnum +"""utilities for introspecting services""" +import enum -class ServiceIntrospectionState(IntEnum): - OFF = ... - METADATA = ... - CONTENTS = ... + +class ServiceIntrospectionState(enum.IntEnum): + OFF = 0 + + METADATA = 1 + + CONTENTS = 2 diff --git a/rclpy/src/rclpy/_rclpy_nanobind.cpp b/rclpy/src/rclpy/_rclpy_nanobind.cpp index 1bb2321a1..d5aa29404 100644 --- a/rclpy/src/rclpy/_rclpy_nanobind.cpp +++ b/rclpy/src/rclpy/_rclpy_nanobind.cpp @@ -75,12 +75,11 @@ NB_MODULE(_rclpy_nanobind, m) { .value("ABORT", GOAL_EVENT_ABORT) .value("CANCELED", GOAL_EVENT_CANCELED); - m.attr("RCL_DEFAULT_DOMAIN_ID") = nb::int_(RCL_DEFAULT_DOMAIN_ID); - m.attr("RMW_DURATION_INFINITE") = nb::int_(rmw_time_total_nsec(RMW_DURATION_INFINITE)); - m.attr("RMW_QOS_DEADLINE_BEST_AVAILABLE") = nb::int_( - rmw_time_total_nsec(RMW_QOS_DEADLINE_BEST_AVAILABLE)); - m.attr("RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE") = nb::int_( - rmw_time_total_nsec(RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE)); + m.attr("RCL_DEFAULT_DOMAIN_ID") = RCL_DEFAULT_DOMAIN_ID; + m.attr("RMW_DURATION_INFINITE") = rmw_time_total_nsec(RMW_DURATION_INFINITE); + m.attr("RMW_QOS_DEADLINE_BEST_AVAILABLE") = rmw_time_total_nsec(RMW_QOS_DEADLINE_BEST_AVAILABLE); + m.attr("RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE") = + rmw_time_total_nsec(RMW_QOS_LIVELINESS_LEASE_DURATION_BEST_AVAILABLE); nb::enum_(m, "ClockChange", nb::is_arithmetic()) .value( @@ -261,7 +260,7 @@ NB_MODULE(_rclpy_nanobind, m) { "Assert the liveliness of an entity."); m.def( - "rclpy_remove_ros_args", &rclpy::remove_ros_args, nb::arg().none(), + "rclpy_remove_ros_args", &rclpy::remove_ros_args, "Remove ROS-specific arguments from argument vector."); rclpy::define_rmw_qos_profile(m); diff --git a/rclpy/src/rclpy/action_client.cpp b/rclpy/src/rclpy/action_client.cpp index 862bd4d47..0707165b8 100644 --- a/rclpy/src/rclpy/action_client.cpp +++ b/rclpy/src/rclpy/action_client.cpp @@ -271,13 +271,13 @@ ActionClient::is_ready(WaitSet & wait_set) void ActionClient::configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); - pub_opts.qos = - pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - nb::cast(pyqos_service_event_pub); + if (pyqos_service_event_pub) { + pub_opts.qos = *pyqos_service_event_pub; + } rcl_ret_t ret = rcl_action_client_configure_action_introspection( rcl_action_client_.get(), node_.rcl_ptr(), clock.rcl_ptr(), @@ -400,7 +400,6 @@ define_action_client(nb::object module) "Take an action status response.") .def( "configure_introspection", &ActionClient::configure_introspection, - nb::arg(), nb::arg().none(), nb::arg(), "Configure whether internal client introspection is enabled") .def( "configure_feedback_subscription_filter_add_goal_id", diff --git a/rclpy/src/rclpy/action_client.hpp b/rclpy/src/rclpy/action_client.hpp index a8c8773b8..08fd5d64d 100644 --- a/rclpy/src/rclpy/action_client.hpp +++ b/rclpy/src/rclpy/action_client.hpp @@ -16,6 +16,7 @@ #define RCLPY__ACTION_CLIENT_HPP_ #include +#include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include "destroyable.hpp" @@ -198,7 +200,7 @@ class ActionClient : public Destroyable, public std::enable_shared_from_this pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Configure content filter for feedback subscription with the given goal ID. diff --git a/rclpy/src/rclpy/action_server.cpp b/rclpy/src/rclpy/action_server.cpp index 9670df4a3..0924df11a 100644 --- a/rclpy/src/rclpy/action_server.cpp +++ b/rclpy/src/rclpy/action_server.cpp @@ -365,13 +365,13 @@ ActionServer::expire_goals(int64_t max_num_goals) void ActionServer::configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); - pub_opts.qos = - pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - nb::cast(pyqos_service_event_pub); + if (pyqos_service_event_pub) { + pub_opts.qos = *pyqos_service_event_pub; + } rcl_ret_t ret = rcl_action_server_configure_action_introspection( rcl_action_server_.get(), node_.rcl_ptr(), clock.rcl_ptr(), @@ -442,7 +442,6 @@ define_action_server(nb::object module) "Add an action entity to a wait set.") .def( "configure_introspection", &ActionServer::configure_introspection, - nb::arg(), nb::arg().none(), nb::arg(), "Configure whether internal service introspection is enabled"); } diff --git a/rclpy/src/rclpy/action_server.hpp b/rclpy/src/rclpy/action_server.hpp index 27be70fd4..1d4457b25 100644 --- a/rclpy/src/rclpy/action_server.hpp +++ b/rclpy/src/rclpy/action_server.hpp @@ -16,12 +16,14 @@ #define RCLPY__ACTION_SERVER_HPP_ #include +#include #include #include #include #include +#include #include "clock.hpp" #include "destroyable.hpp" @@ -259,7 +261,7 @@ class ActionServer : public Destroyable, public std::enable_shared_from_this pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Force an early destruction of this object diff --git a/rclpy/src/rclpy/client.cpp b/rclpy/src/rclpy/client.cpp index e6033f379..f3a6a2a40 100644 --- a/rclpy/src/rclpy/client.cpp +++ b/rclpy/src/rclpy/client.cpp @@ -54,7 +54,8 @@ Client::destroy() } Client::Client( - Node & node, nb::object pysrv_type, const std::string & service_name, nb::object pyqos_profile) + Node & node, nb::object pysrv_type, const std::string & service_name, + std::optional pyqos_profile) : node_(node) { srv_type_ = static_cast(common_get_type_support(pysrv_type)); @@ -64,8 +65,8 @@ Client::Client( rcl_client_options_t client_ops = rcl_client_get_default_options(); - if (!pyqos_profile.is_none()) { - client_ops.qos = nb::cast(pyqos_profile); + if (pyqos_profile) { + client_ops.qos = *pyqos_profile; } // Create a client @@ -148,13 +149,13 @@ Client::take_response(nb::object pyresponse_type) void Client::configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); - pub_opts.qos = - pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - nb::cast(pyqos_service_event_pub); + if (pyqos_service_event_pub) { + pub_opts.qos = *pyqos_service_event_pub; + } rcl_ret_t ret = rcl_client_configure_service_introspection( rcl_client_.get(), node_.rcl_ptr(), clock.rcl_ptr(), srv_type_, pub_opts, introspection_state); @@ -220,9 +221,7 @@ void define_client(nb::object module) { nb::class_(module, "Client") - .def( - nb::init(), - nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def(nb::init>()) .def_prop_ro( "service_name", &Client::get_service_name, "Get the name of the service") @@ -242,7 +241,6 @@ define_client(nb::object module) "Take a received response from an earlier request") .def( "configure_introspection", &Client::configure_introspection, - nb::arg(), nb::arg().none(), nb::arg(), "Configure whether introspection is enabled") .def( "get_logger_name", &Client::get_logger_name, diff --git a/rclpy/src/rclpy/client.hpp b/rclpy/src/rclpy/client.hpp index d83c09ebd..699a155e4 100644 --- a/rclpy/src/rclpy/client.hpp +++ b/rclpy/src/rclpy/client.hpp @@ -16,6 +16,7 @@ #define RCLPY__CLIENT_HPP_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include +#include #include #include "clock.hpp" @@ -53,7 +55,9 @@ class Client : public Destroyable, public std::enable_shared_from_this * \param[in] service_name The service name * \param[in] pyqos QoSProfile python object for this client */ - Client(Node & node, nb::object pysrv_type, const std::string & service_name, nb::object pyqos); + Client( + Node & node, nb::object pysrv_type, const std::string & service_name, + std::optional pyqos); ~Client() = default; @@ -104,7 +108,7 @@ class Client : public Destroyable, public std::enable_shared_from_this */ void configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Force an early destruction of this object diff --git a/rclpy/src/rclpy/events_executor/events_executor.cpp b/rclpy/src/rclpy/events_executor/events_executor.cpp index 78fff8a0b..f54cd0d96 100644 --- a/rclpy/src/rclpy/events_executor/events_executor.cpp +++ b/rclpy/src/rclpy/events_executor/events_executor.cpp @@ -819,8 +819,7 @@ void EventsExecutor::IterateTask(nb::handle task) task.dec_ref(); if (!ex.is_none()) { - // It's not clear how to easily turn a Python exception into a C++ one, so let's just throw - // it again and let nanobind translate it normally. + // Raise() converts the Python exception instance into a C++ nb::python_error. try { Raise(ex); } catch (nb::python_error & cpp_ex) { @@ -828,7 +827,7 @@ void EventsExecutor::IterateTask(nb::handle task) // can use the logger from that, otherwise we'll have to leave it undefined. nb::object logger = nb::none(); if (nodes_.size() == 1) { - logger = nodes_[0].attr("get_logger")(); + logger = (*nodes_.begin()).attr("get_logger")(); } HandleCallbackExceptionWithLogger(cpp_ex, logger, "task"); throw; @@ -881,9 +880,8 @@ logger.warning("Error occurred at:\n" + "".join(traceback.format_tb(exc_trace))) void EventsExecutor::Raise(nb::object ex) { - nb::dict scope; - scope["ex"] = ex; - nb::exec(nb::str("raise ex"), scope); + PyErr_SetObject(reinterpret_cast(Py_TYPE(ex.ptr())), ex.ptr()); + throw nb::python_error(); } // nanobind module bindings diff --git a/rclpy/src/rclpy/node.cpp b/rclpy/src/rclpy/node.cpp index aa37f89c2..3c306c8cb 100644 --- a/rclpy/src/rclpy/node.cpp +++ b/rclpy/src/rclpy/node.cpp @@ -219,13 +219,13 @@ Node::get_names_impl(bool get_enclaves) for (size_t idx = 0; idx < node_names.size; ++idx) { if (get_enclaves) { pynode_names_and_namespaces[idx] = nb::make_tuple( - nb::str(node_names.data[idx]), - nb::str(node_namespaces.data[idx]), - nb::str(enclaves.data[idx])); + node_names.data[idx], + node_namespaces.data[idx], + enclaves.data[idx]); } else { pynode_names_and_namespaces[idx] = nb::make_tuple( - nb::str(node_names.data[idx]), - nb::str(node_namespaces.data[idx])); + node_names.data[idx], + node_namespaces.data[idx]); } } @@ -280,33 +280,33 @@ _parameter_from_rcl_variant( type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_BOOL_ARRAY; nb::list list_value = create_sized_list(variant->bool_array_value->size); for (size_t i = 0; i < variant->bool_array_value->size; ++i) { - list_value[i] = nb::bool_(variant->bool_array_value->values[i]); + list_value[i] = variant->bool_array_value->values[i]; } value = list_value; } else if (variant->integer_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_INTEGER_ARRAY; nb::list list_value = create_sized_list(variant->integer_array_value->size); for (size_t i = 0; i < variant->integer_array_value->size; ++i) { - list_value[i] = nb::int_(variant->integer_array_value->values[i]); + list_value[i] = variant->integer_array_value->values[i]; } value = list_value; } else if (variant->double_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_DOUBLE_ARRAY; nb::list list_value = create_sized_list(variant->double_array_value->size); for (size_t i = 0; i < variant->double_array_value->size; ++i) { - list_value[i] = nb::float_(variant->double_array_value->values[i]); + list_value[i] = variant->double_array_value->values[i]; } value = list_value; } else if (variant->string_array_value) { type_enum_value = rcl_interfaces__msg__ParameterType__PARAMETER_STRING_ARRAY; nb::list list_value = create_sized_list(variant->string_array_value->size); for (size_t i = 0; i < variant->string_array_value->size; ++i) { - list_value[i] = nb::str(variant->string_array_value->data[i]); + list_value[i] = variant->string_array_value->data[i]; } value = list_value; } - nb::object type = pyparameter_type_cls(nb::int_(type_enum_value)); + nb::object type = pyparameter_type_cls(type_enum_value); return pyparameter_cls(pyname, type, value); } @@ -433,10 +433,10 @@ Node::Node( const char * node_name, const char * namespace_, Context & context, - nb::object pycli_args, + std::optional pycli_args, bool use_global_arguments, bool enable_rosout, - nb::object rosout_qos_profile) + std::optional rosout_qos_profile) : context_(context) { rcl_ret_t ret; @@ -446,8 +446,8 @@ Node::Node( std::vector arg_values; const char ** const_arg_values = nullptr; nb::list pyargs; - if (!pycli_args.is_none()) { - pyargs = nb::cast(pycli_args); + if (pycli_args) { + pyargs = *pycli_args; if (!pyargs.empty()) { arg_values.resize(pyargs.size()); for (size_t i = 0; i < pyargs.size(); ++i) { @@ -514,8 +514,8 @@ Node::Node( options.arguments = arguments; options.enable_rosout = enable_rosout; - if (!rosout_qos_profile.is_none()) { - options.rosout_qos = nb::cast(rosout_qos_profile); + if (rosout_qos_profile) { + options.rosout_qos = *rosout_qos_profile; } ret = rcl_node_init( @@ -603,9 +603,9 @@ define_node(nb::object module) { nb::class_(module, "Node") .def( - nb::init(), - nb::arg(), nb::arg(), nb::arg(), nb::arg().none(), nb::arg(), nb::arg(), - nb::arg().none()) + nb::init< + const char *, const char *, Context &, std::optional, bool, bool, + std::optional>()) .def_prop_ro( "pointer", [](const Node & node) { return reinterpret_cast(node.rcl_ptr()); diff --git a/rclpy/src/rclpy/node.hpp b/rclpy/src/rclpy/node.hpp index 8453222f0..8880c967d 100644 --- a/rclpy/src/rclpy/node.hpp +++ b/rclpy/src/rclpy/node.hpp @@ -16,11 +16,13 @@ #define RCLPY__NODE_HPP_ #include +#include #include #include #include +#include #include "context.hpp" #include "destroyable.hpp" @@ -52,10 +54,10 @@ class Node : public Destroyable, public std::enable_shared_from_this const char * node_name, const char * namespace_, Context & context, - nb::object pycli_args, + std::optional pycli_args, bool use_global_arguments, bool enable_rosout, - nb::object rosout_qos_profile); + std::optional rosout_qos_profile); /// Get the fully qualified name of the node. /** diff --git a/rclpy/src/rclpy/publisher.cpp b/rclpy/src/rclpy/publisher.cpp index dc2e6f44e..babeb8a50 100644 --- a/rclpy/src/rclpy/publisher.cpp +++ b/rclpy/src/rclpy/publisher.cpp @@ -33,7 +33,7 @@ namespace rclpy { Publisher::Publisher( Node & node, nb::object pymsg_type, std::string topic, - nb::object pyqos_profile) + std::optional pyqos_profile) : node_(node) { auto msg_type = static_cast( @@ -44,8 +44,8 @@ Publisher::Publisher( rcl_publisher_options_t publisher_ops = rcl_publisher_get_default_options(); - if (!pyqos_profile.is_none()) { - publisher_ops.qos = nb::cast(pyqos_profile); + if (pyqos_profile) { + publisher_ops.qos = *pyqos_profile; } rcl_publisher_ = std::shared_ptr( @@ -160,9 +160,7 @@ void define_publisher(nb::object module) { nb::class_(module, "Publisher") - .def( - nb::init(), - nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def(nb::init>()) .def_prop_ro( "pointer", [](const Publisher & publisher) { return reinterpret_cast(publisher.rcl_ptr()); diff --git a/rclpy/src/rclpy/publisher.hpp b/rclpy/src/rclpy/publisher.hpp index 7c37fafb7..d40445405 100644 --- a/rclpy/src/rclpy/publisher.hpp +++ b/rclpy/src/rclpy/publisher.hpp @@ -16,6 +16,7 @@ #define RCLPY__PUBLISHER_HPP_ #include +#include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include "destroyable.hpp" @@ -53,7 +55,7 @@ class Publisher : public Destroyable, public std::enable_shared_from_this pyqos_profile); /// Get the name of the logger associated with the node of the publisher. /** diff --git a/rclpy/src/rclpy/serialization.cpp b/rclpy/src/rclpy/serialization.cpp index 497ad5092..3e6262def 100644 --- a/rclpy/src/rclpy/serialization.cpp +++ b/rclpy/src/rclpy/serialization.cpp @@ -93,17 +93,9 @@ deserialize(nb::bytes pybuffer, nb::object pymsg_type) // Create a serialized message object rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message(); // Just copy pointer to avoid extra allocation and copy - char * serialized_buffer; - Py_ssize_t length; - if (PyBytes_AsStringAndSize(pybuffer.ptr(), &serialized_buffer, &length)) { - throw nb::python_error(); - } - if (length < 0) { - throw nb::python_error(); - } - serialized_msg.buffer_capacity = length; - serialized_msg.buffer_length = length; - serialized_msg.buffer = reinterpret_cast(serialized_buffer); + serialized_msg.buffer_capacity = pybuffer.size(); + serialized_msg.buffer_length = pybuffer.size(); + serialized_msg.buffer = reinterpret_cast(const_cast(pybuffer.c_str())); auto deserialized_ros_msg = create_from_py(pymsg_type); if (!deserialized_ros_msg) { diff --git a/rclpy/src/rclpy/service.cpp b/rclpy/src/rclpy/service.cpp index 131e08069..21fa1c7e6 100644 --- a/rclpy/src/rclpy/service.cpp +++ b/rclpy/src/rclpy/service.cpp @@ -53,7 +53,7 @@ Service::destroy() Service::Service( Node & node, nb::object pysrv_type, const std::string & service_name, - nb::object pyqos_profile) + std::optional pyqos_profile) : node_(node) { srv_type_ = static_cast( @@ -64,8 +64,8 @@ Service::Service( rcl_service_options_t service_ops = rcl_service_get_default_options(); - if (!pyqos_profile.is_none()) { - service_ops.qos = nb::cast(pyqos_profile); + if (pyqos_profile) { + service_ops.qos = *pyqos_profile; } // Create a service @@ -169,13 +169,13 @@ Service::get_logger_name() const void Service::configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state) { rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options(); - pub_opts.qos = - pyqos_service_event_pub.is_none() ? rcl_publisher_get_default_options().qos : - nb::cast(pyqos_service_event_pub); + if (pyqos_service_event_pub) { + pub_opts.qos = *pyqos_service_event_pub; + } rcl_ret_t ret = rcl_service_configure_service_introspection( rcl_service_.get(), node_.rcl_ptr(), clock.rcl_ptr(), srv_type_, pub_opts, introspection_state); @@ -224,9 +224,7 @@ void define_service(nb::object module) { nb::class_(module, "Service") - .def( - nb::init(), - nb::arg(), nb::arg(), nb::arg(), nb::arg().none()) + .def(nb::init>()) .def_prop_ro( "pointer", [](const Service & service) { return reinterpret_cast(service.rcl_ptr()); @@ -246,7 +244,6 @@ define_service(nb::object module) "Take a request from a given service") .def( "configure_introspection", &Service::configure_introspection, - nb::arg(), nb::arg().none(), nb::arg(), "Configure whether introspection is enabled") .def( "get_logger_name", &Service::get_logger_name, diff --git a/rclpy/src/rclpy/service.hpp b/rclpy/src/rclpy/service.hpp index fa257c784..ab5c0fd2e 100644 --- a/rclpy/src/rclpy/service.hpp +++ b/rclpy/src/rclpy/service.hpp @@ -16,6 +16,7 @@ #define RCLPY__SERVICE_HPP_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include +#include #include #include "clock.hpp" @@ -57,7 +59,7 @@ class Service : public Destroyable, public std::enable_shared_from_this */ Service( Node & node, nb::object pysrv_type, const std::string & service_name, - nb::object pyqos_profile); + std::optional pyqos_profile); Service( Node & node, std::shared_ptr rcl_service); @@ -122,7 +124,7 @@ class Service : public Destroyable, public std::enable_shared_from_this */ void configure_introspection( - Clock & clock, nb::object pyqos_service_event_pub, + Clock & clock, std::optional pyqos_service_event_pub, rcl_service_introspection_state_t introspection_state); /// Force an early destruction of this object diff --git a/rclpy/src/rclpy/subscription.cpp b/rclpy/src/rclpy/subscription.cpp index 2d5bec921..3b378bc73 100644 --- a/rclpy/src/rclpy/subscription.cpp +++ b/rclpy/src/rclpy/subscription.cpp @@ -66,8 +66,8 @@ get_c_vector_string(const std::vector & strings_in) Subscription::Subscription( Node & node, nb::object pymsg_type, std::string topic, - nb::object pyqos_profile, nb::object content_filter_options, - nb::object acceptable_buffer_backends) + std::optional pyqos_profile, nb::object content_filter_options, + std::optional acceptable_buffer_backends) : node_(node) { auto msg_type = static_cast( @@ -78,14 +78,13 @@ Subscription::Subscription( rcl_subscription_options_t subscription_ops = rcl_subscription_get_default_options(); - if (!pyqos_profile.is_none()) { - subscription_ops.qos = nb::cast(pyqos_profile); + if (pyqos_profile) { + subscription_ops.qos = *pyqos_profile; } - if (!acceptable_buffer_backends.is_none()) { - std::string acceptable_backends_str = nb::cast(acceptable_buffer_backends); + if (acceptable_buffer_backends) { rcl_ret_t ret = rcl_subscription_options_set_acceptable_buffer_backends( - acceptable_backends_str.c_str(), + acceptable_buffer_backends->c_str(), &subscription_ops); if (RCL_RET_OK != ret) { throw rclpy::RCLError("Failed to set acceptable_buffer_backends"); @@ -383,11 +382,12 @@ void define_subscription(nb::object module) { nb::class_(module, "Subscription") - .def(nb::init(), + .def(nb::init, + nb::object, std::optional>(), "node"_a, "msg_type"_a, "topic"_a, - "qos_profile"_a.none(), + "qos_profile"_a, "content_filter_options"_a = nb::none(), "acceptable_buffer_backends"_a = nb::none()) .def_prop_ro( diff --git a/rclpy/src/rclpy/subscription.hpp b/rclpy/src/rclpy/subscription.hpp index 7b495adb2..ea99f3304 100644 --- a/rclpy/src/rclpy/subscription.hpp +++ b/rclpy/src/rclpy/subscription.hpp @@ -16,6 +16,7 @@ #define RCLPY__SUBSCRIPTION_HPP_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -57,8 +59,9 @@ class Subscription : public Destroyable, public std::enable_shared_from_this pyqos_profile, + nb::object content_filter_options = nb::none(), + std::optional acceptable_buffer_backends = std::nullopt); /// Take a message and its metadata from a subscription /** diff --git a/rclpy/src/rclpy/utils.cpp b/rclpy/src/rclpy/utils.cpp index 3bd148945..702a32477 100644 --- a/rclpy/src/rclpy/utils.cpp +++ b/rclpy/src/rclpy/utils.cpp @@ -50,10 +50,10 @@ convert_to_py_names_and_types(const rcl_names_and_types_t * names_and_types) for (size_t i = 0u; i < names_and_types->names.size; ++i) { nb::list py_types = create_sized_list(names_and_types->types[i].size); for (size_t j = 0u; j < names_and_types->types[i].size; ++j) { - py_types[j] = nb::str(names_and_types->types[i].data[j]); + py_types[j] = names_and_types->types[i].data[j]; } py_names_and_types[i] = nb::make_tuple( - nb::str(names_and_types->names.data[i]), py_types); + names_and_types->names.data[i], py_types); } return py_names_and_types; } @@ -156,7 +156,7 @@ assert_liveliness(rclpy::Publisher * publisher) } nb::list -remove_ros_args(nb::object pycli_args) +remove_ros_args(std::optional pycli_args) { rcl_ret_t ret; rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -165,8 +165,8 @@ remove_ros_args(nb::object pycli_args) std::vector arg_values; const char ** const_arg_values = NULL; nb::list pyargs; - if (!pycli_args.is_none()) { - pyargs = nb::cast(pycli_args); + if (pycli_args) { + pyargs = *pycli_args; if (!pyargs.empty()) { arg_values.resize(pyargs.size()); for (size_t i = 0; i < pyargs.size(); ++i) { @@ -298,20 +298,20 @@ _convert_to_py_topic_endpoint_info(const rmw_topic_endpoint_info_t * topic_endpo { nb::list py_endpoint_gid = create_sized_list(RMW_GID_STORAGE_SIZE); for (size_t i = 0; i < RMW_GID_STORAGE_SIZE; i++) { - py_endpoint_gid[i] = nb::int_(topic_endpoint_info->endpoint_gid[i]); + py_endpoint_gid[i] = topic_endpoint_info->endpoint_gid[i]; } // Create dictionary that represents rmw_topic_endpoint_info_t nb::dict py_endpoint_info_dict; // Populate keyword arguments // A success returns 0, and a failure returns -1 - py_endpoint_info_dict["node_name"] = nb::str(topic_endpoint_info->node_name); - py_endpoint_info_dict["node_namespace"] = nb::str(topic_endpoint_info->node_namespace); - py_endpoint_info_dict["topic_type"] = nb::str(topic_endpoint_info->topic_type); + py_endpoint_info_dict["node_name"] = topic_endpoint_info->node_name; + py_endpoint_info_dict["node_namespace"] = topic_endpoint_info->node_namespace; + py_endpoint_info_dict["topic_type"] = topic_endpoint_info->topic_type; py_endpoint_info_dict["topic_type_hash"] = convert_to_type_hash_dict(&topic_endpoint_info->topic_type_hash); py_endpoint_info_dict["endpoint_type"] = - nb::int_(static_cast(topic_endpoint_info->endpoint_type)); + static_cast(topic_endpoint_info->endpoint_type); py_endpoint_info_dict["endpoint_gid"] = py_endpoint_gid; py_endpoint_info_dict["qos_profile"] = convert_to_qos_dict(&topic_endpoint_info->qos_profile); @@ -343,7 +343,7 @@ _convert_to_py_service_endpoint_info(const rmw_service_endpoint_info_t * service for(size_t c = 0; c < service_endpoint_info->endpoint_count; c++) { nb::list py_endpoint_gid = create_sized_list(RMW_GID_STORAGE_SIZE); for (size_t i = 0; i < RMW_GID_STORAGE_SIZE; i++) { - py_endpoint_gid[i] = nb::int_(service_endpoint_info->endpoint_gids[c][i]); + py_endpoint_gid[i] = service_endpoint_info->endpoint_gids[c][i]; } py_endpoint_gids.append(py_endpoint_gid); } @@ -351,9 +351,9 @@ _convert_to_py_service_endpoint_info(const rmw_service_endpoint_info_t * service nb::dict py_endpoint_info_dict; // Populate keyword arguments // A success returns 0, and a failure returns -1 - py_endpoint_info_dict["node_name"] = nb::str(service_endpoint_info->node_name); - py_endpoint_info_dict["node_namespace"] = nb::str(service_endpoint_info->node_namespace); - py_endpoint_info_dict["service_type"] = nb::str(service_endpoint_info->service_type); + py_endpoint_info_dict["node_name"] = service_endpoint_info->node_name; + py_endpoint_info_dict["node_namespace"] = service_endpoint_info->node_namespace; + py_endpoint_info_dict["service_type"] = service_endpoint_info->service_type; py_endpoint_info_dict["service_type_hash"] = convert_to_type_hash_dict(&service_endpoint_info->service_type_hash); py_endpoint_info_dict["qos_profiles"] = nb::list(); @@ -369,8 +369,8 @@ _convert_to_py_service_endpoint_info(const rmw_service_endpoint_info_t * service py_endpoint_info_dict["qos_profiles"] = qos_profiles_list; py_endpoint_info_dict["endpoint_gids"] = py_endpoint_gids; py_endpoint_info_dict["endpoint_type"] = - nb::int_(static_cast(service_endpoint_info->endpoint_type)); - py_endpoint_info_dict["endpoint_count"] = nb::int_(service_endpoint_info->endpoint_count); + static_cast(service_endpoint_info->endpoint_type); + py_endpoint_info_dict["endpoint_count"] = service_endpoint_info->endpoint_count; return py_endpoint_info_dict; } @@ -456,17 +456,17 @@ convert_to_qos_dict(const rmw_qos_profile_t * qos_profile) // Create dictionary and populate arguments with QoSProfile object nb::dict pyqos_kwargs; - pyqos_kwargs["depth"] = nb::int_(qos_profile->depth); - pyqos_kwargs["history"] = nb::int_(static_cast(qos_profile->history)); - pyqos_kwargs["reliability"] = nb::int_(static_cast(qos_profile->reliability)); - pyqos_kwargs["durability"] = nb::int_(static_cast(qos_profile->durability)); + pyqos_kwargs["depth"] = qos_profile->depth; + pyqos_kwargs["history"] = static_cast(qos_profile->history); + pyqos_kwargs["reliability"] = static_cast(qos_profile->reliability); + pyqos_kwargs["durability"] = static_cast(qos_profile->durability); pyqos_kwargs["lifespan"] = _convert_rmw_time_to_py_duration(&qos_profile->lifespan); pyqos_kwargs["deadline"] = _convert_rmw_time_to_py_duration(&qos_profile->deadline); - pyqos_kwargs["liveliness"] = nb::int_(static_cast(qos_profile->liveliness)); + pyqos_kwargs["liveliness"] = static_cast(qos_profile->liveliness); pyqos_kwargs["liveliness_lease_duration"] = _convert_rmw_time_to_py_duration( &qos_profile->liveliness_lease_duration); pyqos_kwargs["avoid_ros_namespace_conventions"] = - nb::bool_(qos_profile->avoid_ros_namespace_conventions); + qos_profile->avoid_ros_namespace_conventions; return pyqos_kwargs; } @@ -477,7 +477,7 @@ convert_to_type_hash_dict(const rosidl_type_hash_t * type_hash) // Create dictionary and populate arguments with type hash object nb::dict type_hash_kwargs; - type_hash_kwargs["version"] = nb::int_(type_hash->version); + type_hash_kwargs["version"] = type_hash->version; type_hash_kwargs["value"] = nb::bytes( reinterpret_cast(type_hash->value), ROSIDL_TYPE_HASH_SIZE); diff --git a/rclpy/src/rclpy/utils.hpp b/rclpy/src/rclpy/utils.hpp index 0fbc9e83f..9316ed6af 100644 --- a/rclpy/src/rclpy/utils.hpp +++ b/rclpy/src/rclpy/utils.hpp @@ -16,6 +16,7 @@ #define RCLPY__UTILS_HPP_ #include +#include #include #include // rcl_names_and_types_t @@ -24,6 +25,7 @@ #include #include +#include #include "publisher.hpp" @@ -137,7 +139,7 @@ assert_liveliness(rclpy::Publisher * publisher); * \return Parsed list of strings */ nb::list -remove_ros_args(nb::object pycli_args); +remove_ros_args(std::optional pycli_args); /// Throw UnparsedROSArgsError with a message saying which args are unparsed. void From 6ec8dcc083afa200c8615c2c6ee90520e75bd949 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Fri, 7 Aug 2026 09:02:18 -0400 Subject: [PATCH 3/4] Fix comment about PythonEqHandler Signed-off-by: Sebastian Castro --- rclpy/src/rclpy/events_executor/python_eq_handler.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rclpy/src/rclpy/events_executor/python_eq_handler.hpp b/rclpy/src/rclpy/events_executor/python_eq_handler.hpp index 2148ad9c0..d1c339cf4 100644 --- a/rclpy/src/rclpy/events_executor/python_eq_handler.hpp +++ b/rclpy/src/rclpy/events_executor/python_eq_handler.hpp @@ -23,10 +23,9 @@ namespace rclpy { namespace events_executor { -/// This is intended to be used as the KeyEqual template arg to STL containers using a -/// nb::handle as a Key, comparing keys by Python object identity. nanobind defines no -/// `operator==` for handles (callers must pick identity or equality explicitly via -/// `.is()` or `.equal()`), so the default std::equal_to would not compile. +/// This is intended to be used as the KeyEqual template arg to STL containers +/// using a nb::handle as a Key, comparing keys by Python object identity. +/// nanobind defines no `operator==` for handles. struct PythonEqHandler { inline auto operator()(const nb::handle & x, const nb::handle & y) const From f1dbafaa197744272ea3b5ef8113594260d5b424 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Tue, 25 Aug 2026 22:50:18 -0400 Subject: [PATCH 4/4] Re-incorporate generics in generated stubs Signed-off-by: Sebastian Castro --- rclpy/CMakeLists.txt | 3 +- rclpy/rclpy/impl/_rclpy_nanobind.pyi | 112 +++++++++++++++++---------- rclpy/src/rclpy/_rclpy_nanobind.cpp | 13 ++++ rclpy/src/rclpy/action_client.cpp | 49 +++++++++--- rclpy/src/rclpy/action_server.cpp | 57 +++++++++++--- rclpy/src/rclpy/client.cpp | 18 ++++- rclpy/src/rclpy/event_handle.cpp | 19 ++++- rclpy/src/rclpy/publisher.cpp | 13 +++- rclpy/src/rclpy/service.cpp | 20 ++++- rclpy/src/rclpy/subscription.cpp | 19 ++++- rclpy/stubgen_pattern.pat | 27 +++++++ 11 files changed, 266 insertions(+), 84 deletions(-) create mode 100644 rclpy/stubgen_pattern.pat diff --git a/rclpy/CMakeLists.txt b/rclpy/CMakeLists.txt index 7d1efffb6..4d24c9d27 100644 --- a/rclpy/CMakeLists.txt +++ b/rclpy/CMakeLists.txt @@ -144,7 +144,8 @@ nanobind_add_stub(_rclpy_nanobind_stub MODULE _rclpy_nanobind OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/rclpy/impl/_rclpy_nanobind.pyi" PYTHON_PATH "${CMAKE_CURRENT_BINARY_DIR}/test_rclpy" - DEPENDS _rclpy_nanobind + PATTERN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/stubgen_pattern.pat" + DEPENDS _rclpy_nanobind "${CMAKE_CURRENT_SOURCE_DIR}/stubgen_pattern.pat" ) nanobind_add_stub(_rclpy_nanobind_service_introspection_stub MODULE _rclpy_nanobind.service_introspection diff --git a/rclpy/rclpy/impl/_rclpy_nanobind.pyi b/rclpy/rclpy/impl/_rclpy_nanobind.pyi index 808d3ce8a..817072e6f 100644 --- a/rclpy/rclpy/impl/_rclpy_nanobind.pyi +++ b/rclpy/rclpy/impl/_rclpy_nanobind.pyi @@ -2,11 +2,39 @@ from collections.abc import Callable, Sequence import enum -from typing import overload +from typing import Any, Generic, TypeVar, overload + +from action_msgs.msg import GoalInfo, GoalStatusArray +from action_msgs.srv._cancel_goal import ( + CancelGoal_Request, + CancelGoal_Response +) +from rclpy.subscription import MessageInfo +from rclpy.subscription_content_filter_options import ( + ContentFilterOptions +) +from rclpy.type_support import ( + Action, + FeedbackMessage, + FeedbackT, + GetResultServiceRequest, + GetResultServiceResponse, + GoalT, + ImplT, + MsgT, + ResultT, + SendGoalServiceRequest, + SendGoalServiceResponse, + Srv, + SrvRequestT, + SrvResponseT +) from . import service_introspection as service_introspection +T = TypeVar("T") + class Destroyable: def __enter__(self) -> None: ... @@ -108,8 +136,8 @@ class NotImplementedError(NotImplementedError): class InvalidHandle(RuntimeError): pass -class Client(Destroyable): - def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... +class Client(Destroyable, Generic[SrvRequestT, SrvResponseT]): + def __init__(self, node: Node, srv_type: type[Srv[SrvRequestT, SrvResponseT]], srv_name: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None: ... @property def service_name(self) -> str: @@ -119,13 +147,13 @@ class Client(Destroyable): def pointer(self) -> int: """Get the address of the entity as an integer""" - def send_request(self, arg: object, /) -> int: + def send_request(self, pyrequest: SrvRequestT, /) -> int: """Send a request""" def service_server_is_available(self) -> bool: """Return true if the service server is available""" - def take_response(self, arg: object, /) -> tuple: + def take_response(self, pyresponse_type: type[SrvResponseT], /) -> tuple[rmw_service_info_t, SrvResponseT] | tuple[None, None]: """Take a received response from an earlier request""" def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: @@ -160,8 +188,8 @@ class rcl_duration_t: @property def nanoseconds(self) -> int: ... -class Publisher(Destroyable): - def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... +class Publisher(Destroyable, Generic[MsgT]): + def __init__(self, node: Node, msg_type: type[MsgT], topic: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None: ... @property def pointer(self) -> int: @@ -176,7 +204,7 @@ class Publisher(Destroyable): def get_topic_name(self) -> str: """Retrieve the topic name from a Publisher.""" - def publish(self, arg: object, /) -> None: + def publish(self, msg: MsgT, /) -> None: """Publish a message""" def publish_raw(self, arg: bytes, /) -> None: @@ -185,8 +213,8 @@ class Publisher(Destroyable): def wait_for_all_acked(self, arg: rcl_duration_t, /) -> bool: """Wait until all published message data is acknowledged""" -class Service(Destroyable): - def __init__(self, arg0: Node, arg1: object, arg2: str, arg3: rmw_qos_profile_t | None) -> None: ... +class Service(Destroyable, Generic[SrvRequestT, SrvResponseT]): + def __init__(self, node: Node, pysrv_type: type[Srv[SrvRequestT, SrvResponseT]], name: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None: ... @property def pointer(self) -> int: @@ -200,10 +228,10 @@ class Service(Destroyable): def qos(self) -> dict: """Get the qos profile of the service""" - def service_send_response(self, arg0: object, arg1: rmw_request_id_t, /) -> None: + def service_send_response(self, pyresponse: SrvResponseT, header: rmw_request_id_t, /) -> None: """Send a response""" - def service_take_request(self, arg: object, /) -> tuple: + def service_take_request(self, pyrequest_type: type[SrvRequestT], /) -> tuple[SrvRequestT, rmw_service_info_t] | tuple[None, None]: """Take a request from a given service""" def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: @@ -243,32 +271,32 @@ class rmw_request_id_t: def rclpy_qos_check_compatible(arg0: rmw_qos_profile_t, arg1: rmw_qos_profile_t, /) -> QoSCheckCompatibleResult: """Check if two QoS profiles are compatible.""" -class ActionClient(Destroyable): - def __init__(self, node: Node, action_type: object, action_name: str, goal_service_qos_profile: rmw_qos_profile_t, result_service_qos_profile: rmw_qos_profile_t, cancel_service_qos_profile: rmw_qos_profile_t, feedback_sub_qos_profile: rmw_qos_profile_t, status_sub_qos_profile: rmw_qos_profile_t, enable_feedback_msg_optimization: bool = False) -> None: ... +class ActionClient(Destroyable, Generic[GoalT, ResultT, FeedbackT, ImplT]): + def __init__(self, node: Node, action_type: type[Action[GoalT, ResultT, FeedbackT, ImplT]], action_name: str, goal_service_qos_profile: rmw_qos_profile_t, result_service_qos_profile: rmw_qos_profile_t, cancel_service_qos_profile: rmw_qos_profile_t, feedback_sub_qos_profile: rmw_qos_profile_t, status_sub_qos_profile: rmw_qos_profile_t, enable_feedback_msg_optimization: bool = False) -> None: ... @property def pointer(self) -> int: """Get the address of the entity as an integer""" - def take_goal_response(self, arg: object, /) -> tuple: + def take_goal_response(self, pymsg_type: type[SendGoalServiceResponse], /) -> tuple[int, SendGoalServiceResponse] | tuple[None, None]: """Take an action goal response.""" - def send_result_request(self, arg: object, /) -> int: + def send_result_request(self, pyrequest: GetResultServiceRequest, /) -> int: """Send an action result request.""" - def take_cancel_response(self, arg: object, /) -> tuple: + def take_cancel_response(self, pymsg_type: type[CancelGoal_Response], /) -> tuple[int, CancelGoal_Response] | tuple[None, None]: """Take an action cancel response.""" - def take_feedback(self, arg: object, /) -> object: + def take_feedback(self, pymsg_type: type[FeedbackMessage[FeedbackT]], /) -> FeedbackMessage[FeedbackT] | None: """Take a feedback message from a given action client.""" - def send_cancel_request(self, arg: object, /) -> int: + def send_cancel_request(self, pyrequest: CancelGoal_Request, /) -> int: """Send an action cancel request.""" - def send_goal_request(self, arg: object, /) -> int: + def send_goal_request(self, pyrequest: SendGoalServiceRequest[GoalT], /) -> int: """Send an action goal request.""" - def take_result_response(self, arg: object, /) -> tuple: + def take_result_response(self, pymsg_type: type[GetResultServiceResponse[ResultT]], /) -> tuple[int, GetResultServiceResponse[ResultT]] | tuple[None, None]: """Take an action result response.""" def get_num_entities(self) -> tuple: @@ -283,7 +311,7 @@ class ActionClient(Destroyable): def is_ready(self, arg: WaitSet, /) -> tuple: """Check if an action entity has any ready wait set entities.""" - def take_status(self, arg: object, /) -> object: + def take_status(self, pymsg_type: type[GoalStatusArray], /) -> GoalStatusArray | None: """Take an action status response.""" def configure_introspection(self, arg0: Clock, arg1: rmw_qos_profile_t | None, arg2: service_introspection.ServiceIntrospectionState) -> None: @@ -311,32 +339,32 @@ class ActionGoalHandle(Destroyable): def is_active(self) -> bool: """Check if a goal is active.""" -class ActionServer(Destroyable): - def __init__(self, arg0: Node, arg1: Clock, arg2: object, arg3: str, arg4: rmw_qos_profile_t, arg5: rmw_qos_profile_t, arg6: rmw_qos_profile_t, arg7: rmw_qos_profile_t, arg8: rmw_qos_profile_t, arg9: float, /) -> None: ... +class ActionServer(Destroyable, Generic[GoalT, ResultT, FeedbackT, ImplT]): + def __init__(self, node: Node, rclpy_clock: Clock, pyaction_type: type[Action[GoalT, ResultT, FeedbackT, ImplT]], action_name: str, goal_service_qos: rmw_qos_profile_t, result_service_qos: rmw_qos_profile_t, cancel_service_qos: rmw_qos_profile_t, feedback_topic_qos: rmw_qos_profile_t, status_topic_qos: rmw_qos_profile_t, result_timeout: float, /) -> None: ... @property def pointer(self) -> int: """Get the address of the entity as an integer""" - def take_goal_request(self, arg: object, /) -> tuple: + def take_goal_request(self, pymsg_type: type[SendGoalServiceRequest[GoalT]], /) -> tuple[rmw_request_id_t, SendGoalServiceRequest[GoalT]] | tuple[None, None]: """Take an action goal request.""" - def send_goal_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: + def send_goal_response(self, header: rmw_request_id_t, pyresponse: SendGoalServiceResponse, /) -> None: """Send an action goal response.""" - def send_result_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: + def send_result_response(self, header: rmw_request_id_t, pyresponse: GetResultServiceResponse[ResultT], /) -> None: """Send an action result response.""" - def take_cancel_request(self, arg: object, /) -> tuple: + def take_cancel_request(self, pymsg_type: type[CancelGoal_Request], /) -> tuple[rmw_request_id_t, CancelGoal_Request] | tuple[None, None]: """Take an action cancel request.""" - def take_result_request(self, arg: object, /) -> tuple: + def take_result_request(self, pymsg_type: type[GetResultServiceRequest], /) -> tuple[rmw_request_id_t, GetResultServiceRequest] | tuple[None, None]: """Take an action result request.""" - def send_cancel_response(self, arg0: rmw_request_id_t, arg1: object, /) -> None: + def send_cancel_response(self, header: rmw_request_id_t, pyresponse: CancelGoal_Response, /) -> None: """Send an action cancel response.""" - def publish_feedback(self, arg: object, /) -> None: + def publish_feedback(self, pymsg: FeedbackT, /) -> None: """Publish a feedback message from a given action server.""" def publish_status(self) -> None: @@ -345,13 +373,13 @@ class ActionServer(Destroyable): def notify_goal_done(self) -> None: """Notify goal is done.""" - def goal_exists(self, arg: object, /) -> bool: + def goal_exists(self, pygoal_info: GoalInfo, /) -> bool: """Check is a goal exists in the server.""" - def process_cancel_request(self, arg0: object, arg1: object, /) -> object: + def process_cancel_request(self, pycancel_request: CancelGoal_Request, pycancel_response_type: type[CancelGoal_Response], /) -> CancelGoal_Response: """Process a cancel request""" - def expire_goals(self, arg: int, /) -> tuple: + def expire_goals(self, max_num_goals: int, /) -> tuple[GoalInfo, ...]: """Expired goals.""" def get_num_entities(self) -> tuple: @@ -422,14 +450,14 @@ class Timer(Destroyable): def clear_on_reset_callback(self) -> None: ... -class Subscription(Destroyable): - def __init__(self, node: Node, msg_type: object, topic: str, qos_profile: rmw_qos_profile_t | None, content_filter_options: object | None = None, acceptable_buffer_backends: str | None = None) -> None: ... +class Subscription(Destroyable, Generic[MsgT]): + def __init__(self, node: Node, msg_type: type[MsgT], topic: str, qos_profile: rmw_qos_profile_t | None, content_filter_options: ContentFilterOptions | None = None, acceptable_buffer_backends: str | None = None) -> None: ... @property def pointer(self) -> int: """Get the address of the entity as an integer""" - def take_message(self, arg0: object, arg1: bool, /) -> object: + def take_message(self, pymsg_type: type[MsgT], raw: bool, /) -> tuple[MsgT | bytes, MessageInfo] | None: """Take a message and its metadata from a subscription""" def get_logger_name(self) -> str: @@ -458,7 +486,7 @@ class Subscription(Destroyable): Set the filter expression and expression parameters for the subscription. """ - def get_content_filter(self) -> object: + def get_content_filter(self) -> ContentFilterOptions: """ Get the filter expression and expression parameters for the subscription. """ @@ -683,18 +711,18 @@ class Node(Destroyable): def get_parameters(self, arg: object, /) -> dict: """Get a list of parameters for the current node""" -class EventHandle(Destroyable): +class EventHandle(Destroyable, Generic[T]): @overload - def __init__(self, arg0: Subscription, arg1: rcl_subscription_event_type_t, /) -> None: ... + def __init__(self, subscription: Subscription[Any], event_type: rcl_subscription_event_type_t, /) -> None: ... @overload - def __init__(self, arg0: Publisher, arg1: rcl_publisher_event_type_t, /) -> None: ... + def __init__(self, publisher: Publisher[Any], event_type: rcl_publisher_event_type_t, /) -> None: ... @property def pointer(self) -> int: """Get the address of the entity as an integer""" - def take_event(self) -> object: + def take_event(self) -> T | None: """Get pending data from a ready event""" class rcl_subscription_event_type_t(enum.IntEnum): diff --git a/rclpy/src/rclpy/_rclpy_nanobind.cpp b/rclpy/src/rclpy/_rclpy_nanobind.cpp index d5aa29404..69d79e2c4 100644 --- a/rclpy/src/rclpy/_rclpy_nanobind.cpp +++ b/rclpy/src/rclpy/_rclpy_nanobind.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include @@ -60,6 +61,18 @@ namespace nb = nanobind; NB_MODULE(_rclpy_nanobind, m) { m.doc() = "ROS 2 Python client library."; + // rclpy imports this extension as "rclpy._rclpy_nanobind", but the stub + // generator loads it as the top-level module "_rclpy_nanobind". Register the + // canonical name too, so that importing rclpy afterwards reuses this already + // initialized module instead of loading the extension a second time. + nb::module_::import_("sys").attr("modules")["rclpy._rclpy_nanobind"] = m; + + // Type variable for the generic classes in the generated stubs. + // The other type variables from rclpy.type_support that nb::sig() + // strings reference are imported into the stub by stubgen_pattern.pat. + // ``T`` has no equivalent there, so it is declared as a module attribute. + m.attr("T") = nb::type_var("T"); + rclpy::define_destroyable(m); nb::enum_(m, "ClockType", nb::is_arithmetic()) diff --git a/rclpy/src/rclpy/action_client.cpp b/rclpy/src/rclpy/action_client.cpp index 0707165b8..44c639874 100644 --- a/rclpy/src/rclpy/action_client.cpp +++ b/rclpy/src/rclpy/action_client.cpp @@ -343,7 +343,9 @@ ActionClient::configure_feedback_subscription_filter_remove_goal_id(nb::bytes go void define_action_client(nb::object module) { - nb::class_(module, "ActionClient") + nb::class_( + module, "ActionClient", nb::is_generic(), + nb::sig("class ActionClient(Destroyable, typing.Generic[GoalT, ResultT, FeedbackT, ImplT])")) .def( nb::init None")) .def_prop_ro( "pointer", [](const ActionClient & action_client) { return reinterpret_cast(action_client.rcl_ptr()); @@ -364,25 +375,41 @@ define_action_client(nb::object module) "Get the address of the entity as an integer") .def( "take_goal_response", &ActionClient::take_goal_response, - "Take an action goal response.") + "Take an action goal response.", + nb::sig( + "def take_goal_response(self, pymsg_type: type[SendGoalServiceResponse], /)" + " -> tuple[int, SendGoalServiceResponse] | tuple[None, None]")) .def( "send_result_request", &ActionClient::send_result_request, - "Send an action result request.") + "Send an action result request.", + nb::sig("def send_result_request(self, pyrequest: GetResultServiceRequest, /) -> int")) .def( "take_cancel_response", &ActionClient::take_cancel_response, - "Take an action cancel response.") + "Take an action cancel response.", + nb::sig( + "def take_cancel_response(self, pymsg_type: type[CancelGoal_Response], /)" + " -> tuple[int, CancelGoal_Response] | tuple[None, None]")) .def( "take_feedback", &ActionClient::take_feedback, - "Take a feedback message from a given action client.") + "Take a feedback message from a given action client.", + nb::sig( + "def take_feedback(self, pymsg_type: type[FeedbackMessage[FeedbackT]], /)" + " -> FeedbackMessage[FeedbackT] | None")) .def( "send_cancel_request", &ActionClient::send_cancel_request, - "Send an action cancel request.") + "Send an action cancel request.", + nb::sig( + "def send_cancel_request(self, pyrequest: CancelGoal_Request, /) -> int")) .def( "send_goal_request", &ActionClient::send_goal_request, - "Send an action goal request.") + "Send an action goal request.", + nb::sig("def send_goal_request(self, pyrequest: SendGoalServiceRequest[GoalT], /) -> int")) .def( "take_result_response", &ActionClient::take_result_response, - "Take an action result response.") + "Take an action result response.", + nb::sig( + "def take_result_response(self, pymsg_type: type[GetResultServiceResponse[ResultT]], /)" + " -> tuple[int, GetResultServiceResponse[ResultT]] | tuple[None, None]")) .def( "get_num_entities", &ActionClient::get_num_entities, "Get the number of wait set entities that make up an action entity.") @@ -397,7 +424,9 @@ define_action_client(nb::object module) "Check if an action entity has any ready wait set entities.") .def( "take_status", &ActionClient::take_status, - "Take an action status response.") + "Take an action status response.", + nb::sig( + "def take_status(self, pymsg_type: type[GoalStatusArray], /) -> GoalStatusArray | None")) .def( "configure_introspection", &ActionClient::configure_introspection, "Configure whether internal client introspection is enabled") diff --git a/rclpy/src/rclpy/action_server.cpp b/rclpy/src/rclpy/action_server.cpp index 0924df11a..44173bba7 100644 --- a/rclpy/src/rclpy/action_server.cpp +++ b/rclpy/src/rclpy/action_server.cpp @@ -385,11 +385,20 @@ ActionServer::configure_introspection( void define_action_server(nb::object module) { - nb::class_(module, "ActionServer") + nb::class_( + module, "ActionServer", nb::is_generic(), + nb::sig("class ActionServer(Destroyable, typing.Generic[GoalT, ResultT, FeedbackT, ImplT])")) .def( nb::init()) + const rmw_qos_profile_t &, const rmw_qos_profile_t &, double>(), + nb::sig( + "def __init__(self, node: Node, rclpy_clock: Clock, " + "pyaction_type: type[Action[GoalT, ResultT, FeedbackT, ImplT]], " + "action_name: str, goal_service_qos: rmw_qos_profile_t, " + "result_service_qos: rmw_qos_profile_t, cancel_service_qos: rmw_qos_profile_t, " + "feedback_topic_qos: rmw_qos_profile_t, status_topic_qos: rmw_qos_profile_t, " + "result_timeout: float, /) -> None")) .def_prop_ro( "pointer", [](const ActionServer & action_server) { return reinterpret_cast(action_server.rcl_ptr()); @@ -397,25 +406,44 @@ define_action_server(nb::object module) "Get the address of the entity as an integer") .def( "take_goal_request", &ActionServer::take_goal_request, - "Take an action goal request.") + "Take an action goal request.", + nb::sig( + "def take_goal_request(self, pymsg_type: type[SendGoalServiceRequest[GoalT]], /)" + " -> tuple[rmw_request_id_t, SendGoalServiceRequest[GoalT]] | tuple[None, None]")) .def( "send_goal_response", &ActionServer::send_goal_response, - "Send an action goal response.") + "Send an action goal response.", + nb::sig( + "def send_goal_response(self, header: rmw_request_id_t, " + "pyresponse: SendGoalServiceResponse, /) -> None")) .def( "send_result_response", &ActionServer::send_result_response, - "Send an action result response.") + "Send an action result response.", + nb::sig( + "def send_result_response(self, header: rmw_request_id_t, " + "pyresponse: GetResultServiceResponse[ResultT], /) -> None")) .def( "take_cancel_request", &ActionServer::take_cancel_request, - "Take an action cancel request.") + "Take an action cancel request.", + nb::sig( + "def take_cancel_request(self, pymsg_type: type[CancelGoal_Request], /)" + " -> tuple[rmw_request_id_t, CancelGoal_Request] | tuple[None, None]")) .def( "take_result_request", &ActionServer::take_result_request, - "Take an action result request.") + "Take an action result request.", + nb::sig( + "def take_result_request(self, pymsg_type: type[GetResultServiceRequest], /)" + " -> tuple[rmw_request_id_t, GetResultServiceRequest] | tuple[None, None]")) .def( "send_cancel_response", &ActionServer::send_cancel_response, - "Send an action cancel response.") + "Send an action cancel response.", + nb::sig( + "def send_cancel_response(self, header: rmw_request_id_t, " + "pyresponse: CancelGoal_Response, /) -> None")) .def( "publish_feedback", &ActionServer::publish_feedback, - "Publish a feedback message from a given action server.") + "Publish a feedback message from a given action server.", + nb::sig("def publish_feedback(self, pymsg: FeedbackT, /) -> None")) .def( "publish_status", &ActionServer::publish_status, "Publish a status message from a given action server.") @@ -424,13 +452,18 @@ define_action_server(nb::object module) "Notify goal is done.") .def( "goal_exists", &ActionServer::goal_exists, - "Check is a goal exists in the server.") + "Check is a goal exists in the server.", + nb::sig("def goal_exists(self, pygoal_info: GoalInfo, /) -> bool")) .def( "process_cancel_request", &ActionServer::process_cancel_request, - "Process a cancel request") + "Process a cancel request", + nb::sig( + "def process_cancel_request(self, pycancel_request: CancelGoal_Request, " + "pycancel_response_type: type[CancelGoal_Response], /) -> CancelGoal_Response")) .def( "expire_goals", &ActionServer::expire_goals, - "Expired goals.") + "Expired goals.", + nb::sig("def expire_goals(self, max_num_goals: int, /) -> tuple[GoalInfo, ...]")) .def( "get_num_entities", &ActionServer::get_num_entities, "Get the number of wait set entities that make up an action entity.") diff --git a/rclpy/src/rclpy/client.cpp b/rclpy/src/rclpy/client.cpp index f3a6a2a40..dab148466 100644 --- a/rclpy/src/rclpy/client.cpp +++ b/rclpy/src/rclpy/client.cpp @@ -220,8 +220,14 @@ Client::clear_on_new_response_callback() void define_client(nb::object module) { - nb::class_(module, "Client") - .def(nb::init>()) + nb::class_( + module, "Client", nb::is_generic(), + nb::sig("class Client(Destroyable, typing.Generic[SrvRequestT, SrvResponseT])")) + .def( + nb::init>(), + nb::sig( + "def __init__(self, node: Node, srv_type: type[Srv[SrvRequestT, SrvResponseT]], " + "srv_name: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None")) .def_prop_ro( "service_name", &Client::get_service_name, "Get the name of the service") @@ -232,13 +238,17 @@ define_client(nb::object module) "Get the address of the entity as an integer") .def( "send_request", &Client::send_request, - "Send a request") + "Send a request", + nb::sig("def send_request(self, pyrequest: SrvRequestT, /) -> int")) .def( "service_server_is_available", &Client::service_server_is_available, "Return true if the service server is available") .def( "take_response", &Client::take_response, - "Take a received response from an earlier request") + "Take a received response from an earlier request", + nb::sig( + "def take_response(self, pyresponse_type: type[SrvResponseT], /)" + " -> tuple[rmw_service_info_t, SrvResponseT] | tuple[None, None]")) .def( "configure_introspection", &Client::configure_introspection, "Configure whether introspection is enabled") diff --git a/rclpy/src/rclpy/event_handle.cpp b/rclpy/src/rclpy/event_handle.cpp index dcb58bf9e..9cb3544a8 100644 --- a/rclpy/src/rclpy/event_handle.cpp +++ b/rclpy/src/rclpy/event_handle.cpp @@ -178,9 +178,19 @@ subscription_event_type_is_supported(rcl_subscription_event_type_t event_type) void define_event_handle(nb::module_ module) { - nb::class_(module, "EventHandle") - .def(nb::init()) - .def(nb::init()) + nb::class_( + module, "EventHandle", nb::is_generic(), + nb::sig("class EventHandle(Destroyable, typing.Generic[T])")) + .def( + nb::init(), + nb::sig( + "def __init__(self, subscription: Subscription[typing.Any], " + "event_type: rcl_subscription_event_type_t, /) -> None")) + .def( + nb::init(), + nb::sig( + "def __init__(self, publisher: Publisher[typing.Any], " + "event_type: rcl_publisher_event_type_t, /) -> None")) .def_prop_ro( "pointer", [](const EventHandle & event) { return reinterpret_cast(event.rcl_ptr()); @@ -188,7 +198,8 @@ define_event_handle(nb::module_ module) "Get the address of the entity as an integer") .def( "take_event", &EventHandle::take_event, - "Get pending data from a ready event"); + "Get pending data from a ready event", + nb::sig("def take_event(self) -> T | None")); nb::enum_( module, "rcl_subscription_event_type_t", nb::is_arithmetic()) diff --git a/rclpy/src/rclpy/publisher.cpp b/rclpy/src/rclpy/publisher.cpp index babeb8a50..c2bb41eff 100644 --- a/rclpy/src/rclpy/publisher.cpp +++ b/rclpy/src/rclpy/publisher.cpp @@ -159,8 +159,14 @@ Publisher::wait_for_all_acked(rcl_duration_t pytimeout) void define_publisher(nb::object module) { - nb::class_(module, "Publisher") - .def(nb::init>()) + nb::class_( + module, "Publisher", nb::is_generic(), + nb::sig("class Publisher(Destroyable, typing.Generic[MsgT])")) + .def( + nb::init>(), + nb::sig( + "def __init__(self, node: Node, msg_type: type[MsgT], " + "topic: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None")) .def_prop_ro( "pointer", [](const Publisher & publisher) { return reinterpret_cast(publisher.rcl_ptr()); @@ -177,7 +183,8 @@ define_publisher(nb::object module) "Retrieve the topic name from a Publisher.") .def( "publish", &Publisher::publish, - "Publish a message") + "Publish a message", + nb::sig("def publish(self, msg: MsgT, /) -> None")) .def( "publish_raw", &Publisher::publish_raw, "Publish a serialized message.") diff --git a/rclpy/src/rclpy/service.cpp b/rclpy/src/rclpy/service.cpp index 21fa1c7e6..6e0667a97 100644 --- a/rclpy/src/rclpy/service.cpp +++ b/rclpy/src/rclpy/service.cpp @@ -223,8 +223,14 @@ Service::clear_on_new_request_callback() void define_service(nb::object module) { - nb::class_(module, "Service") - .def(nb::init>()) + nb::class_( + module, "Service", nb::is_generic(), + nb::sig("class Service(Destroyable, typing.Generic[SrvRequestT, SrvResponseT])")) + .def( + nb::init>(), + nb::sig( + "def __init__(self, node: Node, pysrv_type: type[Srv[SrvRequestT, SrvResponseT]], " + "name: str, pyqos_profile: rmw_qos_profile_t | None, /) -> None")) .def_prop_ro( "pointer", [](const Service & service) { return reinterpret_cast(service.rcl_ptr()); @@ -238,10 +244,16 @@ define_service(nb::object module) "Get the qos profile of the service") .def( "service_send_response", &Service::service_send_response, - "Send a response") + "Send a response", + nb::sig( + "def service_send_response(self, pyresponse: SrvResponseT, " + "header: rmw_request_id_t, /) -> None")) .def( "service_take_request", &Service::service_take_request, - "Take a request from a given service") + "Take a request from a given service", + nb::sig( + "def service_take_request(self, pyrequest_type: type[SrvRequestT], /)" + " -> tuple[SrvRequestT, rmw_service_info_t] | tuple[None, None]")) .def( "configure_introspection", &Service::configure_introspection, "Configure whether introspection is enabled") diff --git a/rclpy/src/rclpy/subscription.cpp b/rclpy/src/rclpy/subscription.cpp index 3b378bc73..fbcbc2492 100644 --- a/rclpy/src/rclpy/subscription.cpp +++ b/rclpy/src/rclpy/subscription.cpp @@ -381,7 +381,9 @@ Subscription::get_content_filter() const void define_subscription(nb::object module) { - nb::class_(module, "Subscription") + nb::class_( + module, "Subscription", nb::is_generic(), + nb::sig("class Subscription(Destroyable, typing.Generic[MsgT])")) .def(nb::init, nb::object, std::optional>(), "node"_a, @@ -389,7 +391,12 @@ define_subscription(nb::object module) "topic"_a, "qos_profile"_a, "content_filter_options"_a = nb::none(), - "acceptable_buffer_backends"_a = nb::none()) + "acceptable_buffer_backends"_a = nb::none(), + nb::sig( + "def __init__(self, node: Node, msg_type: type[MsgT], topic: str, " + "qos_profile: rmw_qos_profile_t | None, " + "content_filter_options: ContentFilterOptions | None = None, " + "acceptable_buffer_backends: str | None = None) -> None")) .def_prop_ro( "pointer", [](const Subscription & subscription) { return reinterpret_cast(subscription.rcl_ptr()); @@ -397,7 +404,10 @@ define_subscription(nb::object module) "Get the address of the entity as an integer") .def( "take_message", &Subscription::take_message, - "Take a message and its metadata from a subscription") + "Take a message and its metadata from a subscription", + nb::sig( + "def take_message(self, pymsg_type: type[MsgT], raw: bool, /)" + " -> tuple[MsgT | bytes, MessageInfo] | None")) .def( "get_logger_name", &Subscription::get_logger_name, "Get the name of the logger associated with the node of the subscription.") @@ -420,6 +430,7 @@ define_subscription(nb::object module) "Set the filter expression and expression parameters for the subscription.") .def( "get_content_filter", &Subscription::get_content_filter, - "Get the filter expression and expression parameters for the subscription."); + "Get the filter expression and expression parameters for the subscription.", + nb::sig("def get_content_filter(self) -> ContentFilterOptions")); } } // namespace rclpy diff --git a/rclpy/stubgen_pattern.pat b/rclpy/stubgen_pattern.pat new file mode 100644 index 000000000..cb1515230 --- /dev/null +++ b/rclpy/stubgen_pattern.pat @@ -0,0 +1,27 @@ +# Injects the imports needed by the custom nb::sig() signatures in the C++ bindings, +# which reference rclpy and action_msgs types by their short names. +# The entry below matches the module-level TypeVar ``T`` and re-declares it verbatim. +# The \from escapes add the imports to the top of the generated stub. +_rclpy_nanobind\.T$: + \from typing import TypeVar + \from action_msgs.msg import GoalInfo + \from action_msgs.msg import GoalStatusArray + \from action_msgs.srv._cancel_goal import CancelGoal_Request + \from action_msgs.srv._cancel_goal import CancelGoal_Response + \from rclpy.subscription import MessageInfo + \from rclpy.subscription_content_filter_options import ContentFilterOptions + \from rclpy.type_support import Action + \from rclpy.type_support import FeedbackMessage + \from rclpy.type_support import FeedbackT + \from rclpy.type_support import GetResultServiceRequest + \from rclpy.type_support import GetResultServiceResponse + \from rclpy.type_support import GoalT + \from rclpy.type_support import ImplT + \from rclpy.type_support import MsgT + \from rclpy.type_support import ResultT + \from rclpy.type_support import SendGoalServiceRequest + \from rclpy.type_support import SendGoalServiceResponse + \from rclpy.type_support import Srv + \from rclpy.type_support import SrvRequestT + \from rclpy.type_support import SrvResponseT + T = TypeVar("T")