Creating a Python Library (for ament_python)
Let us start, as already recommended in this tutorial, with a template by ros2 pkg create.
cd ~/ros2_tutorial_workspace/src
ros2 pkg create python_package_with_a_library \
--build-type ament_python \
--library-name sample_python_library
which outputs the forever beautiful wall of text we’re now used to, with a minor difference regarding the additional library template, as highlighted below.
going to create a new package
package name: python_package_with_a_library
destination directory: /home/murilo/git/ROS2_Tutorial/ros2_tutorial_workspace/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['murilo <murilomarinho@ieee.org>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
library_name: sample_python_library
creating folder ./python_package_with_a_library
creating ./python_package_with_a_library/package.xml
creating source folder
creating folder ./python_package_with_a_library/python_package_with_a_library
creating ./python_package_with_a_library/setup.py
creating ./python_package_with_a_library/setup.cfg
creating folder ./python_package_with_a_library/resource
creating ./python_package_with_a_library/resource/python_package_with_a_library
creating ./python_package_with_a_library/python_package_with_a_library/__init__.py
creating folder ./python_package_with_a_library/test
creating ./python_package_with_a_library/test/test_copyright.py
creating ./python_package_with_a_library/test/test_flake8.py
creating ./python_package_with_a_library/test/test_pep257.py
creating folder ./python_package_with_a_library/python_package_with_a_library/sample_python_library
creating ./python_package_with_a_library/python_package_with_a_library/sample_python_library/__init__.py
[WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0
The folders/files, Mason, what do they mean?
The ROS2 package created from the template has a structure like so. In particular, we can see that python_package_with_a_library
is repeated twice in a row. This is a common source of error, so don’t forget!
python_package_with_a_library
└── python_package_with_a_library
└── sample_python_library
__init__.py
__init__.py
└── resource
python_package_with_a_library
└── test
package.xml
setup.cfg
setup.py
We learned the meaning of most of those in the preamble, namely (Murilo’s) Python Best Practices. To quickly clarify a few things, see the table below.
File/Directory |
Meaning |
---|---|
|
The ROS2 package folder. |
|
The Python package, as we saw in the preamble. |
|
The module corresponding to our sample library. |
|
A file for ROS2 to index this package correctly. See Resource file. |
|
The folder contaning the tests, as we already saw in the preamble. |
|
Used by setup.py, see setup.cfg docs. |
|
The instructions to make the package installable, as we saw in the preamble. |
Overview of the library
Hint
If you have created the bad habit of declaring all/too many things in your __init__.py
file, take the hint and start breaking the definitions into different files and use the __init__.py
just to export the relevant parts of your library.
For the sake of the example, let us create a library with a Python function
and another one with a class
. To guide our next steps, we first draw a quick overview of what our python_package_with_a_library
will look like.
python_package_with_a_library
└── python_package_with_a_library
└── sample_python_library
__init__.py
_sample_class.py
_sample_function.py
__init__.py
└── resource
└── test
With respect to the highlighted files, we will
Create the
_sample_function.py
.Create the
_sample_class.py
.Modify
__init__.py
to use the new function and class.
All other files and directories will remain as-is, in the way they were generated by ros2 pkg create.
Create the sample function
Create a new file with the following contents and name.
1def sample_function_for_square_of_sum(a: float, b: float) -> float:
2 """Returns the square of a sum (a + b)^2 = a^2 + 2ab + b^2"""
3 return a**2 + 2*a*b + b**2
The function has two parameters, a
and b
. For simplicity, we’re expecting arguments of type float
and returning a float
, but it could be any Python function.
Create the sample class
Create a new file with the following contents and name.
1class SampleClass:
2 """A sample class to check how they can be imported by other ROS2 packages."""
3
4 def __init__(self, name: str):
5 self._name = name
6
7 def get_name(self) -> str:
8 """
9 Gets the name of this instance.
10 :return: This name.
11 """
12 return self._name
The class is quite simple with a private data member and a method to retrieve it.
Modify the __init__.py
to export the symbols
With the necessary files created and properly organized, the last step is to import
the function and the class. We modify proper __init__.py
file with the following contents.
1from python_package_with_a_library.sample_python_library._sample_class import SampleClass
2from python_package_with_a_library.sample_python_library._sample_function import sample_function_for_square_of_sum
Modify the setup.py
to export the packages
Warning
This step might be unnecessary after this fix.
Note
This is a one-size-fits-most solution, which might not work for certain Python package structures. As a generic solution, we will export all Python packages in the ROS2 package excluding the test directory. For more information on setuptools, see the official Python packaging docs.
~/ros2_tutorial_workspace/src/python_package_with_a_library/setup.py
1from setuptools import setup, find_packages
2
3package_name = 'python_package_with_a_library'
4
5setup(
6 name=package_name,
7 version='0.0.0',
8 packages=find_packages(exclude=['test']),
9 data_files=[
10 ('share/ament_index/resource_index/packages',
11 ['resource/' + package_name]),
12 ('share/' + package_name, ['package.xml']),
13 ],
14 install_requires=['setuptools'],
15 zip_safe=True,
16 maintainer='murilo',
17 maintainer_email='murilomarinho@ieee.org',
18 description='TODO: Package description',
19 license='TODO: License declaration',
20 tests_require=['pytest'],
21 entry_points={
22 'console_scripts': [
23 ],
24 },
25)
Build and source
No surprise here, right?
cd ~/ros2_tutorial_workspace
colcon build
source install/setup.bash
Note
If you don’t remember why we’re building with these commands, see Always source after you build.
If it builds without any unexpected issues, we’re good to go!