Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python Bindings for Publisher, Subscriber and Service Request features. #411

Merged
merged 39 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6937a73
Creates base structure for bindings
Voldivh May 18, 2023
27fe57b
Adds bindings and python module
Voldivh May 30, 2023
7682d37
Adds more bindings and modifies the package structure
Voldivh Jun 1, 2023
21393d2
Make a wrapper for Node::Publisher instead of inheriting from it
azeey Jun 1, 2023
8e88079
Adds bindings and wrapper for subscription methods
Voldivh Jun 5, 2023
833b382
Use message types in `advertise` and `subscribe` signatures
azeey Jun 7, 2023
183be20
Use copy constructor of Publisher
azeey Jun 7, 2023
62b4052
Fix compiler warning, use more specific headers
azeey Jun 7, 2023
7f29e5b
Adds the request_raw bindings
Voldivh Jun 14, 2023
8d64f78
Adds integration test for python bindings
Voldivh Jun 19, 2023
9b281c1
Adds empty line at the end of the gitignore file
Voldivh Jun 20, 2023
03d5984
Adds empty line at the end of the gitignore file
Voldivh Jun 20, 2023
9478c5c
Merge branch 'main' into voldivh/pybind11_bindings
ahcorde Jun 21, 2023
acff6ea
Adds license and addresses code format
Voldivh Jun 22, 2023
05838d6
Adds requester test and fixes license year
Voldivh Jun 23, 2023
b34544c
Removes comment
Voldivh Jun 23, 2023
5402433
Adds the pybind11 dependency to the packages.apt file
Voldivh Jun 23, 2023
830ae5f
Adds other python dependencies
Voldivh Jun 23, 2023
d12a0e4
Adds other python dependencies
Voldivh Jun 23, 2023
9b45618
Adds documentation, removes unnecesary binding argument and skip bind…
Voldivh Jun 26, 2023
a9d660e
Corrects format on python files, create python type hints to methods,…
Voldivh Jun 26, 2023
d4a7119
Adds a new constructor to the node and create bindings for the NodeOp…
Voldivh Jun 27, 2023
684c02b
Adds bindings for Options, EnableStats and TopicStats of the Node class
Voldivh Jun 27, 2023
5756147
Adds a threading lock to the test to avoid data races
Voldivh Jun 27, 2023
2d3c294
Fixes style and add more documentation
Voldivh Jun 28, 2023
d408d94
Adds docstring to python wrapper
Voldivh Jun 29, 2023
77b76ec
Adds more test cases to pubSub
Voldivh Jun 30, 2023
f88ee99
Adds test for rest of bindings
Voldivh Jun 30, 2023
498dd05
Adds MessagePublisher and ServicePublisher bindings for info methods
Voldivh Jul 3, 2023
2f23947
Adds an example using a gz-sim world
Voldivh Jul 5, 2023
aafd3bd
Fixes different tests
Voldivh Jul 6, 2023
9552d5e
Merge branch 'gazebosim:gz-transport12' into voldivh/pybind11_bindings
Voldivh Jul 12, 2023
0e3a5b3
Adds data race examples and modifies docstrings
Voldivh Jul 12, 2023
99531f2
Merge branch 'voldivh/pybind11_bindings' of github.com:Voldivh/gz-tra…
Voldivh Jul 12, 2023
703c9a9
Modifies test name and adds comments on topic stats test
Voldivh Jul 14, 2023
1bfc4fd
Merge branch 'main' into voldivh/pybind11_bindings
Voldivh Jul 17, 2023
e1bca5e
Add python3-gz-msgs10 package dependency
azeey Jul 19, 2023
3bfb365
Adds file for python module generation on CI
Voldivh Jul 20, 2023
5371b41
Rename after_cmake.sh to after_make.sh
azeey Jul 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ build_*
# OS generated files
.DS_Store
*.swp

# Python generated files
*.pyc
50 changes: 48 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,25 @@ set(GZ_CMAKE_VER ${gz-cmake3_VERSION_MAJOR})
#============================================================================
# Configure the project
#============================================================================
option(SKIP_PYBIND11
"Skip generating Python bindings via pybind11"
OFF)

# Python interfaces vars
include(CMakeDependentOption)
include(GzPython)
cmake_dependent_option(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION
"Install python modules in standard system paths in the system"
OFF "NOT SKIP_PYBIND11" OFF)

cmake_dependent_option(USE_DIST_PACKAGES_FOR_PYTHON
"Use dist-packages instead of site-package to install python modules"
OFF "NOT SKIP_PYBIND11" OFF)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

gz_configure_project(VERSION_SUFFIX pre1)
gz_configure_project(VERSION_SUFFIX)
azeey marked this conversation as resolved.
Show resolved Hide resolved

#============================================================================
# Set project-specific options
Expand Down Expand Up @@ -117,14 +132,45 @@ gz_find_package(SQLite3
#============================================================================
# Configure the build
#============================================================================

########################################
# Python interfaces
if (NOT PYTHON3_FOUND)
azeey marked this conversation as resolved.
Show resolved Hide resolved
GZ_BUILD_WARNING("Python is missing: Python interfaces are disabled.")
message (STATUS "Searching for Python - not found.")
else()
message (STATUS "Searching for Python - found version ${Python3_VERSION}.")

if (SKIP_PYBIND11)
message(STATUS "SKIP_PYBIND11 set - disabling python bindings")
else()
set(PYBIND11_PYTHON_VERSION 3)
find_package(pybind11 2.4 QUIET)

if (${pybind11_FOUND})
find_package(Python3 ${GZ_PYTHON_VERSION} REQUIRED COMPONENTS Development)
message (STATUS "Searching for pybind11 - found version ${pybind11_VERSION}.")
else()
GZ_BUILD_WARNING("pybind11 is missing: Python interfaces are disabled.")
message (STATUS "Searching for pybind11 - not found.")
endif()
endif()
endif()

gz_configure_build(QUIT_IF_BUILD_ERRORS
COMPONENTS log parameters)

azeey marked this conversation as resolved.
Show resolved Hide resolved
#============================================================================
# gz command line support
#============================================================================
add_subdirectory(conf)

#============================================================================
# gz transport python bindings
#============================================================================
if (pybind11_FOUND AND NOT SKIP_PYBIND11)
add_subdirectory(python)
endif()

#============================================================================
# Create package information
#============================================================================
Expand Down
98 changes: 98 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
# pybind11 logic for setting up a debug build when both a debug and release
# python interpreter are present in the system seems to be pretty much broken.
# This works around the issue.
set(PYTHON_LIBRARIES "${PYTHON_DEBUG_LIBRARIES}")
endif()

if(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION)
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c "if True:
from distutils import sysconfig as sc
print(sc.get_python_lib(plat_specific=True))"
OUTPUT_VARIABLE Python3_SITEARCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
# Get install variable from Python3 module
# Python3_SITEARCH is available from 3.12 on, workaround if needed:
find_package(Python3 COMPONENTS Interpreter)
endif()

if(USE_DIST_PACKAGES_FOR_PYTHON)
string(REPLACE "site-packages" "dist-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITEARCH})
else()
# custom cmake command is returning dist-packages
string(REPLACE "dist-packages" "site-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITEARCH})
endif()
else()
# If not a system installation, respect local paths
set(GZ_PYTHON_INSTALL_PATH ${GZ_LIB_INSTALL_DIR}/python/gz)
endif()

# Set the build location and install location for a CPython extension
function(configure_build_install_location _library_name)
# Install library for actual use
install(TARGETS ${_library_name}
DESTINATION "${GZ_PYTHON_INSTALL_PATH}/transport${PROJECT_VERSION_MAJOR}"
)
endfunction()

message(STATUS "Building pybind11 interfaces")
set(BINDINGS_MODULE_NAME "_transport")
azeey marked this conversation as resolved.
Show resolved Hide resolved
# Split from main extension and converted to pybind11
pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE
src/transport/_gz_transport_pybind11.cc
)

target_link_libraries(${BINDINGS_MODULE_NAME} PRIVATE
${PROJECT_LIBRARY_TARGET_NAME}
)

target_compile_definitions(${BINDINGS_MODULE_NAME} PRIVATE
BINDINGS_MODULE_NAME=${BINDINGS_MODULE_NAME})

# Deal with naming collision on Windows. It is caused by the both the import
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
# library of sdformat library and the import library of the pybind11 bindings
# library are called sdformat13.lib.
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
set_target_properties(${BINDINGS_MODULE_NAME} PROPERTIES
ARCHIVE_OUTPUT_NAME "python_transport${PROJECT_VERSION_MAJOR}")

configure_build_install_location(${BINDINGS_MODULE_NAME})

install(FILES
src/__init__.py
DESTINATION "${GZ_PYTHON_INSTALL_PATH}/transport${PROJECT_VERSION_MAJOR}"
)

if (BUILD_TESTING AND NOT WIN32)

Voldivh marked this conversation as resolved.
Show resolved Hide resolved
set(python_tests
pubSub_TEST
)
execute_process(COMMAND "${Python3_EXECUTABLE}" -m pytest --version
OUTPUT_VARIABLE PYTEST_output
ERROR_VARIABLE PYTEST_error
RESULT_VARIABLE PYTEST_result)
if(${PYTEST_result} EQUAL 0)
set(pytest_FOUND TRUE)
else()
message("")
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
message(WARNING "Pytest package not available: ${PYTEST_error}")
endif()

foreach (test ${python_tests})
if (pytest_FOUND)
add_test(NAME ${test}.py COMMAND
"${Python3_EXECUTABLE}" -m pytest "${CMAKE_SOURCE_DIR}/python/test/${test}.py" --junitxml "${CMAKE_BINARY_DIR}/test_results/${test}.xml")
else()
add_test(NAME ${test}.py COMMAND
"${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/python/test/${test}.py")
endif()
set(_env_vars)
list(APPEND _env_vars "PYTHONPATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/python/:${CMAKE_BINARY_DIR}/lib:$ENV{PYTHONPATH}")
azeey marked this conversation as resolved.
Show resolved Hide resolved
list(APPEND _env_vars "LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}:$ENV{LD_LIBRARY_PATH}")
set_tests_properties(${test}.py PROPERTIES
ENVIRONMENT "${_env_vars}")
endforeach()
endif()
42 changes: 42 additions & 0 deletions python/examples/publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from gz.msgs10.stringmsg_pb2 import StringMsg
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from gz.msgs10.vector3d_pb2 import Vector3d
from gz.transport13 import AdvertiseMessageOptions
from gz.transport13 import Node

import time

def main():
node = Node()
stringmsg_topic = "/example_stringmsg_topic"
vector3d_topic = "/example_vector3d_topic"
pub_stringmsg = node.advertise(stringmsg_topic, StringMsg)
pub_vector3d = node.advertise(vector3d_topic, Vector3d)

vector3d_msg = Vector3d()
vector3d_msg.x = 10
vector3d_msg.y = 15
vector3d_msg.z = 20

stringmsg_msg = StringMsg()
stringmsg_msg.data = "Hello"
try:
count = 0
while True:
count += 1
vector3d_msg.x = count
# if not pub_stringmsg.publish(stringmsg_msg) or not pub_vector3d.publish(vector3d_msg):
if not pub_vector3d.publish(vector3d_msg):
break
# break

# print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic))
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
# print("Publishing a Vector3d on topic [{}]".format(vector3d_topic))
print(vector3d_msg)
time.sleep(0.00001)

except KeyboardInterrupt:
pass


if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions python/examples/requester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from gz.msgs10.stringmsg_pb2 import StringMsg
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from gz.transport13 import Node

def main():
node = Node()
service_name = "/echo"
request = StringMsg()
request.data = "Hello world"
response = StringMsg()
timeout = 5000

result, response = node.request(service_name, request, StringMsg, StringMsg, timeout, response)
print("Result:", result, "\nResponse:", response.data)

if __name__ == "__main__":
main()
49 changes: 49 additions & 0 deletions python/examples/subscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from gz.msgs10.stringmsg_pb2 import StringMsg
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from gz.msgs10.vector3d_pb2 import Vector3d
from gz.transport13 import SubscribeOptions
from gz.transport13 import Node

import time

def stringmsg_cb(msg: StringMsg):
print("Received StringMsg: [{}]".format(msg.data))

def vector3_cb(msg: Vector3d):
print("Received Vector3: [x: {}, y: {}, z: {}]".format(msg.x, msg.y, msg.z))

def main():
# create a transport node
node = Node()
topic_stringmsg = "/example_stringmsg_topic"
topic_vector3d = "/example_vector3d_topic"

# subscribe to a topic by registering a callback
if node.subscribe(StringMsg, topic_stringmsg, stringmsg_cb):
print("Subscribing to type {} on topic [{}]".format(
StringMsg, topic_stringmsg))
else:
print("Error subscribing to topic [{}]".format(topic_stringmsg))
return

# subscribe to a topic by registering a callback
if node.subscribe(Vector3d, topic_vector3d, vector3_cb):
print("Subscribing to type {} on topic [{}]".format(
Vector3d, topic_vector3d))
else:
print("Error subscribing to topic [{}]".format(topic_vector3d))
return

# wait for shutdown
try:
while True:
time.sleep(0.001)
except KeyboardInterrupt:
pass
# TODO(azeey) The subscriber if we quit without unsubscribing. This may not
azeey marked this conversation as resolved.
Show resolved Hide resolved
# be needed after https://github.com/gazebosim/gz-transport/pull/381.
node.unsubscribe(topic_stringmsg)
node.unsubscribe(topic_vector3d)
print("Done")

if __name__ == "__main__":
main()
35 changes: 35 additions & 0 deletions python/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from ._transport import Node as _Node
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from ._transport import AdvertiseMessageOptions, SubscribeOptions
from importlib import import_module


class Publisher(_Node.Publisher):

def publish(self, proto_msg):
azeey marked this conversation as resolved.
Show resolved Hide resolved
msg_string = proto_msg.SerializeToString()
msg_type = proto_msg.DESCRIPTOR.full_name
return self.publish_raw(msg_string, msg_type)

class Node(_Node):

def advertise(self, topic, msg_type, options=AdvertiseMessageOptions()):
return Publisher(
_Node.advertise(self, topic, msg_type.DESCRIPTOR.full_name,
options))

def subscribe(self, msg_type, topic, callback, options=SubscribeOptions()):

def cb_deserialize(proto_msg, msg_size, msg_info):
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
deserialized_msg = msg_type()
deserialized_msg.ParseFromString(proto_msg)
callback(deserialized_msg)

return self.subscribe_raw(topic, cb_deserialize,
msg_type.DESCRIPTOR.full_name, options)

def request(self, service, request, request_type, response_type, timeout, response):
result, serialized_response = self.request_raw(service, request.SerializeToString(), request_type.DESCRIPTOR.full_name,
azeey marked this conversation as resolved.
Show resolved Hide resolved
response_type.DESCRIPTOR.full_name, timeout, response.SerializeToString())
azeey marked this conversation as resolved.
Show resolved Hide resolved
deserialized_response = response_type()
deserialized_response.ParseFromString(serialized_response)
return result, deserialized_response
Loading