diff --git a/rosidl_cli/package.xml b/rosidl_cli/package.xml index 46f5c7ee5..e45804c7a 100644 --- a/rosidl_cli/package.xml +++ b/rosidl_cli/package.xml @@ -22,6 +22,7 @@ ament_copyright ament_flake8 + ament_mypy ament_pep257 ament_xmllint python3-pytest diff --git a/rosidl_cli/py.typed b/rosidl_cli/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/rosidl_cli/rosidl_cli/cli.py b/rosidl_cli/rosidl_cli/cli.py index 9c3f8ac28..ce729a816 100644 --- a/rosidl_cli/rosidl_cli/cli.py +++ b/rosidl_cli/rosidl_cli/cli.py @@ -14,13 +14,18 @@ import argparse import signal +from typing import Any, List, Union from rosidl_cli.command.generate import GenerateCommand from rosidl_cli.command.translate import TranslateCommand from rosidl_cli.common import get_first_line_doc -def add_subparsers(parser, cli_name, commands): +def add_subparsers( + parser: argparse.ArgumentParser, + cli_name: str, + commands: List[Union[GenerateCommand, TranslateCommand]] +) -> argparse._SubParsersAction[argparse.ArgumentParser]: """ Create argparse subparser for each command. @@ -63,7 +68,7 @@ def add_subparsers(parser, cli_name, commands): return subparser -def main(): +def main() -> Union[str, signal.Signals, Any]: script_name = 'rosidl' description = f'{script_name} is an extensible command-line tool ' \ 'for ROS interface generation.' @@ -74,7 +79,8 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter ) - commands = [GenerateCommand(), TranslateCommand()] + commands: List[Union[GenerateCommand, TranslateCommand]] = \ + [GenerateCommand(), TranslateCommand()] # add arguments for command extension(s) add_subparsers( diff --git a/rosidl_cli/rosidl_cli/command/__init__.py b/rosidl_cli/rosidl_cli/command/__init__.py index 22d035bb5..187a6c187 100644 --- a/rosidl_cli/rosidl_cli/command/__init__.py +++ b/rosidl_cli/rosidl_cli/command/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse + class Command: """ @@ -22,8 +24,8 @@ class Command: * `add_arguments` """ - def add_arguments(self, parser): + def add_arguments(self, parser: argparse.ArgumentParser) -> None: pass - def main(self, *, parser, args): + def main(self, *, args: argparse.Namespace) -> None: raise NotImplementedError() diff --git a/rosidl_cli/rosidl_cli/command/generate/__init__.py b/rosidl_cli/rosidl_cli/command/generate/__init__.py index ee46a937a..b12b946c1 100644 --- a/rosidl_cli/rosidl_cli/command/generate/__init__.py +++ b/rosidl_cli/rosidl_cli/command/generate/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse import pathlib from rosidl_cli.command import Command @@ -24,7 +25,7 @@ class GenerateCommand(Command): name = 'generate' - def add_arguments(self, parser): + def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( '-o', '--output-path', metavar='PATH', type=pathlib.Path, default=None, @@ -50,7 +51,7 @@ def add_arguments(self, parser): "If prefixed by another path followed by a colon ':', " 'path resolution is performed against such path.')) - def main(self, *, args): + def main(self, *, args: argparse.Namespace) -> None: generate( package_name=args.package_name, interface_files=args.interface_files, diff --git a/rosidl_cli/rosidl_cli/command/generate/api.py b/rosidl_cli/rosidl_cli/command/generate/api.py index ebec89144..ff7edbbcb 100644 --- a/rosidl_cli/rosidl_cli/command/generate/api.py +++ b/rosidl_cli/rosidl_cli/command/generate/api.py @@ -14,20 +14,22 @@ import os import pathlib +from typing import List, Optional +from .extensions import GenerateCommandExtension from .extensions import load_type_extensions from .extensions import load_typesupport_extensions def generate( *, - package_name, - interface_files, - include_paths=None, - output_path=None, - types=None, - typesupports=None -): + package_name: str, + interface_files: List[str], + include_paths: Optional[List[str]] = None, + output_path: Optional[pathlib.Path] = None, + types: Optional[List[str]] = None, + typesupports: Optional[List[str]] = None +) -> List[List[str]]: """ Generate source code from interface definition files. @@ -60,7 +62,7 @@ def generate( :returns: list of lists of paths to generated source code files, one group per type or type support extension invoked """ - extensions = [] + extensions: List[GenerateCommandExtension] = [] unspecific_generation = not types and not typesupports diff --git a/rosidl_cli/rosidl_cli/command/generate/extensions.py b/rosidl_cli/rosidl_cli/command/generate/extensions.py index fde1fb6b0..a89630d71 100644 --- a/rosidl_cli/rosidl_cli/command/generate/extensions.py +++ b/rosidl_cli/rosidl_cli/command/generate/extensions.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path +from typing import cast, List, Optional + from rosidl_cli.extensions import Extension from rosidl_cli.extensions import load_extensions @@ -26,11 +29,11 @@ class GenerateCommandExtension(Extension): def generate( self, - package_name, - interface_files, - include_paths, - output_path - ): + package_name: str, + interface_files: List[str], + include_paths: List[str], + output_path: Path + ) -> List[str]: """ Generate source code. @@ -48,11 +51,17 @@ def generate( raise NotImplementedError() -def load_type_extensions(**kwargs): +def load_type_extensions(*, specs: Optional[List[str]], + strict: bool) -> List[GenerateCommandExtension]: """Load extensions for type representation source code generation.""" - return load_extensions('rosidl_cli.command.generate.type_extensions', **kwargs) + extensions = load_extensions('rosidl_cli.command.generate.type_extensions', specs=specs, + strict=strict) + return cast(List[GenerateCommandExtension], extensions) -def load_typesupport_extensions(**kwargs): +def load_typesupport_extensions(*, specs: Optional[List[str]], strict: bool + ) -> List[GenerateCommandExtension]: """Load extensions for type support source code generation.""" - return load_extensions('rosidl_cli.command.generate.typesupport_extensions', **kwargs) + extensions = load_extensions('rosidl_cli.command.generate.typesupport_extensions', + specs=specs, strict=strict) + return cast(List[GenerateCommandExtension], extensions) diff --git a/rosidl_cli/rosidl_cli/command/helpers.py b/rosidl_cli/rosidl_cli/command/helpers.py index f23cc9a88..d81b0cdc3 100644 --- a/rosidl_cli/rosidl_cli/command/helpers.py +++ b/rosidl_cli/rosidl_cli/command/helpers.py @@ -17,9 +17,10 @@ import os import pathlib import tempfile +from typing import Generator, List, Tuple -def package_name_from_interface_file_path(path): +def package_name_from_interface_file_path(path: pathlib.Path) -> str: """ Derive ROS package name from a ROS interface definition file path. @@ -29,7 +30,7 @@ def package_name_from_interface_file_path(path): return pathlib.Path(os.path.abspath(path)).parents[1].name -def dependencies_from_include_paths(include_paths): +def dependencies_from_include_paths(include_paths: List[str]) -> List[str]: """ Collect dependencies' ROS interface definition files from include paths. @@ -45,7 +46,7 @@ def dependencies_from_include_paths(include_paths): }) -def interface_path_as_tuple(path): +def interface_path_as_tuple(path: str) -> Tuple[pathlib.Path, pathlib.Path]: """ Express interface definition file path as an (absolute prefix, relative path) tuple. @@ -61,18 +62,20 @@ def interface_path_as_tuple(path): """ path_as_string = str(path) if ':' not in path_as_string: - prefix = pathlib.Path.cwd() + prefix_path = pathlib.Path.cwd() else: prefix, _, path = path_as_string.rpartition(':') - prefix = pathlib.Path(os.path.abspath(prefix)) - path = pathlib.Path(path) - if path.is_absolute(): + prefix_path = pathlib.Path(os.path.abspath(prefix)) + path_as_path = pathlib.Path(path) + if path_as_path.is_absolute(): raise ValueError('Interface definition file path ' - f"'{path}' cannot be absolute") - return prefix, path + f"'{path_as_path}' cannot be absolute") + return prefix_path, path_as_path -def idl_tuples_from_interface_files(interface_files): +def idl_tuples_from_interface_files( + interface_files: List[str] +) -> List[str]: """ Express ROS interface definition file paths as IDL tuples. @@ -80,9 +83,9 @@ def idl_tuples_from_interface_files(interface_files): which to resolve it followed by a colon ':'. This function then applies the same logic as `interface_path_as_tuple`. """ - idl_tuples = [] - for path in interface_files: - prefix, path = interface_path_as_tuple(path) + idl_tuples: List[str] = [] + for interface_path in interface_files: + prefix, path = interface_path_as_tuple(interface_path) idl_tuples.append(f'{prefix}:{path.as_posix()}') return idl_tuples @@ -90,12 +93,12 @@ def idl_tuples_from_interface_files(interface_files): @contextlib.contextmanager def legacy_generator_arguments_file( *, - package_name, - interface_files, - include_paths, - templates_path, - output_path -): + package_name: str, + interface_files: List[str], + include_paths: List[str], + templates_path: str, + output_path: str +) -> Generator[str, None, None]: """ Generate a temporary rosidl generator arguments file. @@ -138,10 +141,10 @@ def legacy_generator_arguments_file( def generate_visibility_control_file( *, - package_name, - template_path, - output_path -): + package_name: str, + template_path: str, + output_path: str +) -> None: """ Generate a visibility control file from a template. diff --git a/rosidl_cli/rosidl_cli/command/translate/__init__.py b/rosidl_cli/rosidl_cli/command/translate/__init__.py index 03798db3e..f5b6368f0 100644 --- a/rosidl_cli/rosidl_cli/command/translate/__init__.py +++ b/rosidl_cli/rosidl_cli/command/translate/__init__.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse import pathlib + from rosidl_cli.command import Command from .api import translate @@ -24,7 +26,7 @@ class TranslateCommand(Command): name = 'translate' - def add_arguments(self, parser): + def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( '-o', '--output-path', metavar='PATH', type=pathlib.Path, default=None, @@ -64,7 +66,7 @@ def add_arguments(self, parser): 'path resolution is performed against such path.') ) - def main(self, *, args): + def main(self, *, args: argparse.Namespace) -> None: translate( package_name=args.package_name, interface_files=args.interface_files, diff --git a/rosidl_cli/rosidl_cli/command/translate/api.py b/rosidl_cli/rosidl_cli/command/translate/api.py index b63db278e..a7d18bd45 100644 --- a/rosidl_cli/rosidl_cli/command/translate/api.py +++ b/rosidl_cli/rosidl_cli/command/translate/api.py @@ -15,20 +15,21 @@ import collections import os import pathlib +from typing import DefaultDict, Dict, List, Optional, Union from .extensions import load_translate_extensions def translate( *, - package_name, - interface_files, - output_format, - input_format=None, - include_paths=None, - output_path=None, - translators=None -): + package_name: str, + interface_files: List[str], + output_format: str, + input_format: Optional[str] = None, + include_paths: Optional[List[str]] = None, + output_path: Optional[pathlib.Path] = None, + translators: Optional[List[str]] = None +) -> List[str]: """ Translate interface definition files from one format to another. @@ -64,7 +65,8 @@ def translate( raise RuntimeError('No translate extensions found') if not input_format: - interface_files_per_format = collections.defaultdict(list) + interface_files_per_format: Union[DefaultDict[str, List[str]], + Dict[str, List[str]]] = collections.defaultdict(list) for interface_file in interface_files: input_format = os.path.splitext(interface_file)[-1][1:] interface_files_per_format[input_format].append(interface_file) diff --git a/rosidl_cli/rosidl_cli/command/translate/extensions.py b/rosidl_cli/rosidl_cli/command/translate/extensions.py index f193a4804..10c3ba194 100644 --- a/rosidl_cli/rosidl_cli/command/translate/extensions.py +++ b/rosidl_cli/rosidl_cli/command/translate/extensions.py @@ -11,6 +11,8 @@ # 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 pathlib import Path +from typing import cast, ClassVar, List, Optional from rosidl_cli.extensions import Extension from rosidl_cli.extensions import load_extensions @@ -28,13 +30,16 @@ class TranslateCommandExtension(Extension): * `translate` """ + input_format: ClassVar[str] + output_format: ClassVar[str] + def translate( self, - package_name, - interface_files, - include_paths, - output_path - ): + package_name: str, + interface_files: List[str], + include_paths: List[str], + output_path: Path + ) -> List[str]: """ Translate interface definition files. @@ -57,8 +62,10 @@ def translate( raise NotImplementedError() -def load_translate_extensions(**kwargs): +def load_translate_extensions(*, specs: Optional[List[str]], strict: bool + ) -> List[TranslateCommandExtension]: """Load extensions for interface definition translation.""" - return load_extensions( - 'rosidl_cli.command.translate.extensions', **kwargs + extensions = load_extensions( + 'rosidl_cli.command.translate.extensions', specs=specs, strict=strict ) + return cast(List[TranslateCommandExtension], extensions) diff --git a/rosidl_cli/rosidl_cli/common.py b/rosidl_cli/rosidl_cli/common.py index 1f94c2c63..c0e8c9d42 100644 --- a/rosidl_cli/rosidl_cli/common.py +++ b/rosidl_cli/rosidl_cli/common.py @@ -13,7 +13,7 @@ # limitations under the License. -def get_first_line_doc(any_type): +def get_first_line_doc(any_type: object) -> str: if any_type.__doc__: for line in any_type.__doc__.splitlines(): line = line.strip() diff --git a/rosidl_cli/rosidl_cli/entry_points.py b/rosidl_cli/rosidl_cli/entry_points.py index edab63729..6a79673f9 100644 --- a/rosidl_cli/rosidl_cli/entry_points.py +++ b/rosidl_cli/rosidl_cli/entry_points.py @@ -12,38 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. +import importlib.metadata as importlib_metadata import logging - -try: - import importlib.metadata as importlib_metadata -except ModuleNotFoundError: - import importlib_metadata +import sys +from typing import Any, Dict, List, Optional, Tuple, Union logger = logging.getLogger(__name__) -def get_entry_points(group_name, *, specs=None, strict=False): +def get_entry_points(group_name: str, *, specs: Optional[List[str]] = None, strict: bool = False + ) -> Dict[str, importlib_metadata.EntryPoint]: """ Get entry points from a specific group. - :param str group_name: the name of the entry point group - :param list specs: an optional collection of entry point names to retrieve - :param bool strict: whether to raise or warn on error + :param group_name: the name of the entry point group + :param specs: an optional collection of entry point names to retrieve + :param strict: whether to raise or warn on error :returns: mapping from entry point names to ``EntryPoint`` instances - :rtype: dict """ if specs is not None: - specs = set(specs) + specs_set = set(specs) + else: + specs_set = None entry_points_impl = importlib_metadata.entry_points() - if hasattr(entry_points_impl, 'select'): + # Select does not exist until python 3.10 + if sys.version_info >= (3, 10): groups = entry_points_impl.select(group=group_name) else: - groups = entry_points_impl.get(group_name, []) - entry_points = {} + groups: Union[Tuple[importlib_metadata.EntryPoint, ...], + List[importlib_metadata.EntryPoint]] = entry_points_impl.get(group_name, []) + + entry_points: Dict[str, importlib_metadata.EntryPoint] = {} for entry_point in groups: name = entry_point.name - if specs and name not in specs: + if specs_set and name not in specs_set: continue if name in entry_points: msg = (f"Found duplicate entry point '{name}': " @@ -53,8 +56,8 @@ def get_entry_points(group_name, *, specs=None, strict=False): logger.warning(msg) continue entry_points[name] = entry_point - if specs: - pending = specs - set(entry_points) + if specs_set: + pending = specs_set - set(entry_points) if pending: msg = 'Some specs could not be met: ' msg += ', '.join(map(str, pending)) @@ -64,21 +67,22 @@ def get_entry_points(group_name, *, specs=None, strict=False): return entry_points -def load_entry_points(group_name, *, strict=False, **kwargs): +def load_entry_points(group_name: str, *, specs: Optional[List[str]], + strict: bool = False, + ) -> Dict[str, Any]: """ Load entry points for a specific group. See :py:meth:`get_entry_points` for further reference on additional keyword arguments. - :param str group_name: the name of the entry point group - :param bool strict: whether to raise or warn on error + :param group_name: the name of the entry point group + :param strict: whether to raise or warn on error :returns: mapping from entry point name to loaded entry point - :rtype: dict """ - loaded_entry_points = {} + loaded_entry_points: Dict[str, Any] = {} for name, entry_point in get_entry_points( - group_name, strict=strict, **kwargs + group_name, strict=strict, specs=specs ).items(): try: loaded_entry_points[name] = entry_point.load() diff --git a/rosidl_cli/rosidl_cli/extensions.py b/rosidl_cli/rosidl_cli/extensions.py index 7cc31afa2..bbc28c9e7 100644 --- a/rosidl_cli/rosidl_cli/extensions.py +++ b/rosidl_cli/rosidl_cli/extensions.py @@ -14,10 +14,19 @@ import logging import re +from typing import Any, Dict, Final, List, Optional, Tuple, TYPE_CHECKING, Union from rosidl_cli.entry_points import load_entry_points -import yaml +import yaml # type: ignore[import] + +if TYPE_CHECKING: + from typing import TypedDict + from typing_extensions import NotRequired + + class LoadExtensionsArg(TypedDict): + specs: NotRequired[Optional[List[str]]] + strict: NotRequired[bool] logger = logging.getLogger(__name__) @@ -26,18 +35,18 @@ class Extension: """A generic extension point.""" - def __init__(self, name): + def __init__(self, name: str) -> None: self.__name = name @property - def name(self): + def name(self) -> str: return self.__name -SPECS_PATTERN = re.compile(r'^(\w+)(?:\[(.+)\])?$') +SPECS_PATTERN: Final = re.compile(r'^(\w+)(?:\[(.+)\])?$') -def parse_extension_specification(spec): +def parse_extension_specification(spec: str) -> Tuple[Union[str, Any], Union[Dict[Any, Any], Any]]: """ Parse extension specification. @@ -64,18 +73,18 @@ def parse_extension_specification(spec): return name, kwargs -def load_extensions(group_name, *, specs=None, strict=False): +def load_extensions(group_name: str, *, specs: Optional[List[str]] = None, + strict: bool = False) -> List[Extension]: """ Load extensions for a specific group. - :param str group_name: the name of the extension group - :param list specs: an optional collection of extension specs + :param group_name: the name of the extension group + :param specs: an optional collection of extension specs (see :py:meth:`parse_extension_specification` for spec format) - :param bool strict: whether to raise or warn on error + :param strict: whether to raise or warn on error :returns: a list of :py:class:`Extension` instances - :rtype: list """ - extensions = [] + extensions: List[Extension] = [] if specs is not None: kwargs = dict(map( diff --git a/rosidl_cli/test/rosidl_cli/test_common.py b/rosidl_cli/test/rosidl_cli/test_common.py index 166f8bf0f..ccc4a6cb7 100644 --- a/rosidl_cli/test/rosidl_cli/test_common.py +++ b/rosidl_cli/test/rosidl_cli/test_common.py @@ -15,20 +15,20 @@ from rosidl_cli.common import get_first_line_doc -def test_getting_first_line_from_no_docstring(): +def test_getting_first_line_from_no_docstring() -> None: func = test_getting_first_line_from_no_docstring line = get_first_line_doc(func) assert line == '' -def test_getting_first_line_from_docstring(): +def test_getting_first_line_from_docstring() -> None: """Check it gets the first line.""" func = test_getting_first_line_from_docstring line = get_first_line_doc(func) assert line == 'Check it gets the first line' -def test_getting_first_line_from_multiline_docstring(): +def test_getting_first_line_from_multiline_docstring() -> None: """ Check it really gets the first non-empty line. diff --git a/rosidl_cli/test/rosidl_cli/test_extensions.py b/rosidl_cli/test/rosidl_cli/test_extensions.py index 7e3dd2158..94af493af 100644 --- a/rosidl_cli/test/rosidl_cli/test_extensions.py +++ b/rosidl_cli/test/rosidl_cli/test_extensions.py @@ -17,7 +17,7 @@ from rosidl_cli.extensions import parse_extension_specification -def test_extension_specification_parsing(): +def test_extension_specification_parsing() -> None: with pytest.raises(ValueError): parse_extension_specification('bad[') diff --git a/rosidl_cli/test/rosidl_cli/test_helpers.py b/rosidl_cli/test/rosidl_cli/test_helpers.py index 5a16f684f..bba66d4e9 100644 --- a/rosidl_cli/test/rosidl_cli/test_helpers.py +++ b/rosidl_cli/test/rosidl_cli/test_helpers.py @@ -15,6 +15,7 @@ import json import os import pathlib +from typing import Iterable import pytest @@ -22,7 +23,7 @@ from rosidl_cli.command.helpers import legacy_generator_arguments_file -def test_interface_path_as_tuple(): +def test_interface_path_as_tuple() -> None: prefix, path = interface_path_as_tuple('/tmp:msg/Empty.idl') assert pathlib.Path('msg/Empty.idl') == path assert pathlib.Path(os.path.abspath('/tmp')) == prefix @@ -37,7 +38,7 @@ def test_interface_path_as_tuple(): @pytest.fixture -def current_path(request): +def current_path(request: pytest.FixtureRequest) -> Iterable[pathlib.Path]: path = pathlib.Path(request.module.__file__) path = path.resolve() path = path.parent @@ -49,7 +50,7 @@ def current_path(request): os.chdir(str(cwd)) -def test_legacy_generator_arguments_file(current_path): +def test_legacy_generator_arguments_file(current_path: pathlib.Path) -> None: with legacy_generator_arguments_file( package_name='foo', interface_files=['msg/Foo.idl'], diff --git a/rosidl_cli/test/test_copyright.py b/rosidl_cli/test/test_copyright.py index cf0fae31f..66a7d63eb 100644 --- a/rosidl_cli/test/test_copyright.py +++ b/rosidl_cli/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_cli/test/test_flake8.py b/rosidl_cli/test/test_flake8.py index 27ee1078f..eac16eef9 100644 --- a/rosidl_cli/test/test_flake8.py +++ b/rosidl_cli/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_cli/test/test_mypy.py b/rosidl_cli/test/test_mypy.py new file mode 100644 index 000000000..97e4f502a --- /dev/null +++ b/rosidl_cli/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_cli/test/test_pep257.py b/rosidl_cli/test/test_pep257.py index 0e38a6c60..4ae521a5a 100644 --- a/rosidl_cli/test/test_pep257.py +++ b/rosidl_cli/test/test_pep257.py @@ -18,6 +18,6 @@ @pytest.mark.linter @pytest.mark.pep257 -def test_pep257(): +def test_pep257() -> None: rc = main(argv=[]) assert rc == 0, 'Found code style errors / warnings' diff --git a/rosidl_cli/test/test_xmllint.py b/rosidl_cli/test/test_xmllint.py index f46285e71..08bf7fd78 100644 --- a/rosidl_cli/test/test_xmllint.py +++ b/rosidl_cli/test/test_xmllint.py @@ -18,6 +18,6 @@ @pytest.mark.linter @pytest.mark.xmllint -def test_xmllint(): +def test_xmllint() -> None: rc = main(argv=[]) assert rc == 0, 'Found errors'