diff --git a/rosidl_generator_c/rosidl_generator_c/__init__.py b/rosidl_generator_c/rosidl_generator_c/__init__.py index 7dcd61a40..673e69565 100644 --- a/rosidl_generator_c/rosidl_generator_c/__init__.py +++ b/rosidl_generator_c/rosidl_generator_c/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. from math import isnan +from typing import List + from rosidl_generator_type_description import parse_rihs_string from rosidl_generator_type_description import RIHS01_HASH_VALUE_SIZE from rosidl_parser.definition import AbstractGenericString @@ -29,7 +31,7 @@ from rosidl_pycommon import generate_files -def generate_c(generator_arguments_file, disable_description_codegen=False): +def generate_c(generator_arguments_file, disable_description_codegen: bool = False) -> List[str]: mapping = { 'idl.h.em': '%s.h', 'idl__description.c.em': 'detail/%s__description.c', @@ -47,7 +49,7 @@ def generate_c(generator_arguments_file, disable_description_codegen=False): }) -def prefix_with_bom_if_necessary(content): +def prefix_with_bom_if_necessary(content: str) -> str: try: content.encode('ASCII') except UnicodeError: diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index f138bcf10..ed6f70962 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -14,6 +14,7 @@ from ast import literal_eval from math import isnan +from typing import List from rosidl_parser.definition import AbstractGenericString from rosidl_parser.definition import AbstractNestedType @@ -29,7 +30,7 @@ from rosidl_pycommon import generate_files -def generate_cpp(generator_arguments_file): +def generate_cpp(generator_arguments_file) -> List[str]: mapping = { 'idl.hpp.em': '%s.hpp', 'idl__builder.hpp.em': 'detail/%s__builder.hpp', @@ -42,7 +43,7 @@ def generate_cpp(generator_arguments_file): post_process_callback=prefix_with_bom_if_necessary) -def prefix_with_bom_if_necessary(content): +def prefix_with_bom_if_necessary(content: str) -> str: try: content.encode('ASCII') except UnicodeError: diff --git a/rosidl_pycommon/package.xml b/rosidl_pycommon/package.xml index 9467b4a68..30a49c26b 100644 --- a/rosidl_pycommon/package.xml +++ b/rosidl_pycommon/package.xml @@ -20,6 +20,7 @@ ament_copyright ament_flake8 ament_pep257 + ament_mypy python3-pytest diff --git a/rosidl_pycommon/py.typed b/rosidl_pycommon/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/rosidl_pycommon/rosidl_pycommon/__init__.py b/rosidl_pycommon/rosidl_pycommon/__init__.py index 36d887113..d52ac9608 100644 --- a/rosidl_pycommon/rosidl_pycommon/__init__.py +++ b/rosidl_pycommon/rosidl_pycommon/__init__.py @@ -18,6 +18,7 @@ import pathlib import re import sys +from typing import Any, Callable, Dict, List, Optional import em @@ -31,7 +32,7 @@ from rosidl_parser.parser import parse_idl_file -def convert_camel_case_to_lower_case_underscore(value): +def convert_camel_case_to_lower_case_underscore(value: str) -> str: # insert an underscore before any upper case letter # which is followed by a lower case letter value = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', value) @@ -41,12 +42,14 @@ def convert_camel_case_to_lower_case_underscore(value): return value.lower() -def read_generator_arguments(input_file): +def read_generator_arguments(input_file: str) -> Any: with open(input_file, mode='r', encoding='utf-8') as h: return json.load(h) -def get_newest_modification_time(target_dependencies): +def get_newest_modification_time( + target_dependencies: List[str] +) -> Optional[float]: newest_timestamp = None for dep in target_dependencies: ts = os.path.getmtime(dep) @@ -56,9 +59,10 @@ def get_newest_modification_time(target_dependencies): def generate_files( - generator_arguments_file, mapping, additional_context=None, - keep_case=False, post_process_callback=None -): + generator_arguments_file: str, mapping: Dict[str, str], + additional_context: Optional[Dict[str, bool]] = None, + keep_case: bool = False, post_process_callback: Optional[Callable[[str], str]] = None +) -> List[str]: args = read_generator_arguments(generator_arguments_file) template_basepath = pathlib.Path(args['template_dir']) @@ -67,7 +71,7 @@ def generate_files( 'Could not find template: ' + template_filename latest_target_timestamp = get_newest_modification_time(args['target_dependencies']) - generated_files = [] + generated_files: List[str] = [] type_description_files = {} for description_tuple in args.get('type_description_tuples', []): @@ -128,10 +132,10 @@ def generate_files( return generated_files -template_prefix_path = [] +template_prefix_path: List[pathlib.Path] = [] -def get_template_path(template_name): +def get_template_path(template_name: str) -> pathlib.Path: global template_prefix_path for basepath in template_prefix_path: template_path = basepath / template_name @@ -144,14 +148,16 @@ def get_template_path(template_name): def expand_template( - template_name, data, output_file, minimum_timestamp=None, - template_basepath=None, post_process_callback=None -): + template_name: str, data: Dict[str, Any], output_file: str, + minimum_timestamp: Optional[float] = None, + template_basepath: Optional[pathlib.Path] = None, + post_process_callback: Optional[Callable[[str], str]] = None +) -> None: # in the legacy API the first argument was the path to the template if template_basepath is None: - template_name = pathlib.Path(template_name) - template_basepath = template_name.parent - template_name = template_name.name + template_path = pathlib.Path(template_name) + template_basepath = template_path.parent + template_name = template_path.name global template_prefix_path template_prefix_path.append(template_basepath) @@ -226,14 +232,17 @@ def expand_template( h.write(content) -def _add_helper_functions(data): +def _add_helper_functions(data: Dict[str, Any]) -> None: data['TEMPLATE'] = _expand_template -def _expand_template(template_name, **kwargs): +def _expand_template(template_name: str, **kwargs: Any) -> None: global interpreter template_path = get_template_path(template_name) _add_helper_functions(kwargs) + if interpreter is None: + raise RuntimeError('_expand_template called before expand_template') + with template_path.open('r') as h: interpreter.invoke( 'beforeInclude', name=str(template_path), file=h, locals=kwargs) diff --git a/rosidl_pycommon/test/test_copyright.py b/rosidl_pycommon/test/test_copyright.py index a89fa2726..2db7dabb1 100644 --- a/rosidl_pycommon/test/test_copyright.py +++ b/rosidl_pycommon/test/test_copyright.py @@ -18,6 +18,6 @@ @pytest.mark.copyright @pytest.mark.linter -def test_copyright(): +def test_copyright() -> None: rc = main(argv=['.', 'test']) assert rc == 0, 'Found errors' diff --git a/rosidl_pycommon/test/test_flake8.py b/rosidl_pycommon/test/test_flake8.py index 648bb70f9..7830616b3 100644 --- a/rosidl_pycommon/test/test_flake8.py +++ b/rosidl_pycommon/test/test_flake8.py @@ -18,7 +18,7 @@ @pytest.mark.flake8 @pytest.mark.linter -def test_flake8(): +def test_flake8() -> None: rc, errors = main_with_errors(argv=[]) assert rc == 0, \ 'Found %d code style errors / warnings:\n' % len(errors) + \ diff --git a/rosidl_pycommon/test/test_mypy.py b/rosidl_pycommon/test/test_mypy.py new file mode 100644 index 000000000..97e4f502a --- /dev/null +++ b/rosidl_pycommon/test/test_mypy.py @@ -0,0 +1,23 @@ +# 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 ament_mypy.main import main +import pytest + + +@pytest.mark.mypy +@pytest.mark.linter +def test_mypy() -> None: + rc = main(argv=[]) + assert rc == 0, 'Found type errors!' diff --git a/rosidl_pycommon/test/test_pep257.py b/rosidl_pycommon/test/test_pep257.py index 7a2a3747e..d1d2b8e95 100644 --- a/rosidl_pycommon/test/test_pep257.py +++ b/rosidl_pycommon/test/test_pep257.py @@ -18,6 +18,6 @@ @pytest.mark.linter @pytest.mark.pep257 -def test_pep257(): +def test_pep257() -> None: rc = main(argv=['.', 'test']) assert rc == 0, 'Found code style errors / warnings' diff --git a/rosidl_typesupport_introspection_c/rosidl_typesupport_introspection_c/__init__.py b/rosidl_typesupport_introspection_c/rosidl_typesupport_introspection_c/__init__.py index 8a728f9a5..ab96f68d2 100644 --- a/rosidl_typesupport_introspection_c/rosidl_typesupport_introspection_c/__init__.py +++ b/rosidl_typesupport_introspection_c/rosidl_typesupport_introspection_c/__init__.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List + from rosidl_pycommon import generate_files -def generate_c(generator_arguments_file: str): +def generate_c(generator_arguments_file: str) -> List[str]: """ Generate the C implementation of the type support. diff --git a/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/__init__.py b/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/__init__.py index 7457e2a60..b4ba5ed45 100644 --- a/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/__init__.py +++ b/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/__init__.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List + from rosidl_pycommon import generate_files -def generate_cpp(generator_arguments_file: str): +def generate_cpp(generator_arguments_file: str) -> List[str]: """ Generate the C++ implementation of the type support. diff --git a/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/cli.py b/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/cli.py index a26b5d99d..d9091680d 100644 --- a/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/cli.py +++ b/rosidl_typesupport_introspection_cpp/rosidl_typesupport_introspection_cpp/cli.py @@ -13,6 +13,7 @@ # limitations under the License. import pathlib +from typing import List from ament_index_python import get_package_share_directory @@ -32,7 +33,7 @@ def generate( interface_files, include_paths, output_path - ): + ) -> List[str]: package_share_path = pathlib.Path( get_package_share_directory('rosidl_typesupport_introspection_cpp'))