One of the most common complaints from developers moving into large Python codebases is the difficulty in figuring out type information, and the ease by which type mismatch errors can appear at runtime.
Python 3.5 added support for a type annotation system, described in PEP 484. Python 3.6+ expands this with individual variable annotations (PEP 526). While purely decorative and optional, a tool like mypy can use it to perform static type analysis and catch errors, just like compilers and linters for statically typed languages.
There are limitations to mypy, however. It only knows what it’s explicitly told. Functions and classes without annotations are by default not checked, though they can be configured to default to Any or raise mypy errors.
The ROS 2 build farm is essentially only set up to run colcon test. As a result, any contributor wishing to use mypy currently needs to do so manually and hope that no other changes were made by someone not using annotations, or incorrectly annotating their code. This leads to many packages that are partially annotated, or with incorrect annotations ignored when by falling back to Any.
Seeking a fix that 1) helped us remember to check our contributions and 2) maintains a guarantee that packages that are annotated correctly stay so, we created a mypy linter for ament that can be integrated with the rest of the package test suite, allowing for mypy to be run automatically in the ROS 2 build farm and as part of the CI process. Now we can guarantee type correctness in our python code, and avoid the dreaded type mismatch errors!
ament_lint in action
The ament_lint metapackage defines many common linters that can integrate into the build/test pipeline for ROS 2. The package ament_mypy within handles mypy integration.
To add it as a test within your test suite, you’ll need to make a few changes to your package:
Add ament_mypy as a test dependency in your package.xml
Add pytest as a test requirement in setup.py
Write a test case that invokes ament_mypy and fails accordingly
Add ament_mypy as a testing requirement to CMakeLists.txt, if using CMake
For the first, find the section of your package.xml after the name/author/license information, where the dependencies are declared. Alongside the other depend blocks, add an entry
For setup.py, add the keyword argument
if its not already present.
Finally, we add a file test/test_mypy.py, that contains a call to ament_mypy.main()
When using CMake, you’ll need to pass the CONFIG_FILE arg. In the manual invocation example, that means changing the BUILD_TESTING block as follows (assuming your mypy.ini file is in the same directory as above):
The additional argument means ament_cmake_mypy cannot be auto invoked by ament_lint_auto. If you’re already using ament_lint_auto for other packages, you’ll need to exclude ament_mypy.
To exclude ament_cmake_mypy, set the AMENT_LINT_AUTO_EXCLUDE variable and then manually find and invoke it:
# this must happen before the invocation of ament_package()
Running the Test
To run the test and get output to the console, run the following in your workspace:
colcon test -event-handlers console_direct+
To test only your package:
colcon test --packages-select <YOUR_PACKAGE> --event-handlers console_direct+
About the author
Ted (aka "Arnatious") is a robotics engineer and ROS 1 and 2 developer at Canonical, the company behind Ubuntu.