Skip to content

Commit 9ce68de

Browse files
leander-dsouzaSakshayMahna
authored andcommitted
Mypy nav2 common (ros-navigation#5031)
* Configured mypy for nav2_common Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> * Added nav2_common to the workflow. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> * Removed all instances of Any. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> * Fixed unsafe fixes using ruff. Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> --------- Signed-off-by: Leander Stephen D'Souza <leanderdsouza1234@gmail.com> Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
1 parent 446c514 commit 9ce68de

File tree

5 files changed

+84
-50
lines changed

5 files changed

+84
-50
lines changed

‎.github/workflows/lint.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
matrix:
3030
mypy_packages:
3131
- "nav2_smac_planner"
32+
- "nav2_common"
3233
steps:
3334
- uses: actions/checkout@v4
3435

‎nav2_common/nav2_common/launch/has_node_params.py‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import List, Text
16-
1715
import launch
1816
import yaml
1917

2018

21-
class HasNodeParams(launch.Substitution):
19+
class HasNodeParams(launch.Substitution): # type: ignore[misc]
2220
"""
2321
Substitution that checks if a param file contains parameters for a node.
2422
2523
Used in launch system
2624
"""
2725

2826
def __init__(
29-
self, source_file: launch.SomeSubstitutionsType, node_name: Text
27+
self, source_file: launch.SomeSubstitutionsType, node_name: str
3028
) -> None:
3129
super().__init__()
3230
"""
@@ -39,21 +37,22 @@ def __init__(
3937
# import here to avoid loop
4038
from launch.utilities import normalize_to_list_of_substitutions
4139

42-
self.__source_file = normalize_to_list_of_substitutions(source_file)
40+
self.__source_file: list[launch.Substitution] = \
41+
normalize_to_list_of_substitutions(source_file)
4342
self.__node_name = node_name
4443

4544
@property
46-
def name(self) -> List[launch.Substitution]:
45+
def name(self) -> list[launch.Substitution]:
4746
"""Getter for name."""
4847
return self.__source_file
4948

50-
def describe(self) -> Text:
49+
def describe(self) -> str:
5150
"""Return a description of this substitution as a string."""
5251
return ''
5352

54-
def perform(self, context: launch.LaunchContext) -> Text:
53+
def perform(self, context: launch.LaunchContext) -> str:
5554
yaml_filename = launch.utilities.perform_substitutions(context, self.name)
56-
data = yaml.safe_load(open(yaml_filename, 'r'))
55+
data = yaml.safe_load(open(yaml_filename))
5756

5857
if self.__node_name in data.keys():
5958
return 'True'

‎nav2_common/nav2_common/launch/replace_string.py‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
# limitations under the License.
1414

1515
import tempfile
16-
from typing import Dict, List, Optional, Text
16+
from typing import Optional
1717

1818
import launch
1919

2020

21-
class ReplaceString(launch.Substitution):
21+
class ReplaceString(launch.Substitution): # type: ignore[misc]
2222
"""
2323
Substitution that replaces strings on a given file.
2424
@@ -28,7 +28,7 @@ class ReplaceString(launch.Substitution):
2828
def __init__(
2929
self,
3030
source_file: launch.SomeSubstitutionsType,
31-
replacements: Dict,
31+
replacements: dict[str, launch.SomeSubstitutionsType],
3232
condition: Optional[launch.Condition] = None,
3333
) -> None:
3434
super().__init__()
@@ -37,7 +37,8 @@ def __init__(
3737

3838
# import here to avoid loop
3939

40-
self.__source_file = normalize_to_list_of_substitutions(source_file)
40+
self.__source_file: list[launch.Substitution] = \
41+
normalize_to_list_of_substitutions(source_file)
4142
self.__replacements = {}
4243
for key in replacements:
4344
self.__replacements[key] = normalize_to_list_of_substitutions(
@@ -46,7 +47,7 @@ def __init__(
4647
self.__condition = condition
4748

4849
@property
49-
def name(self) -> List[launch.Substitution]:
50+
def name(self) -> list[launch.Substitution]:
5051
"""Getter for name."""
5152
return self.__source_file
5253

@@ -55,17 +56,19 @@ def condition(self) -> Optional[launch.Condition]:
5556
"""Getter for condition."""
5657
return self.__condition
5758

58-
def describe(self) -> Text:
59+
def describe(self) -> str:
5960
"""Return a description of this substitution as a string."""
6061
return ''
6162

62-
def perform(self, context: launch.LaunchContext) -> Text:
63-
yaml_filename = launch.utilities.perform_substitutions(context, self.name)
63+
def perform(self, context: launch.LaunchContext) -> str:
64+
yaml_filename: str = launch.utilities.perform_substitutions(
65+
context, self.name
66+
)
6467
if self.__condition is None or self.__condition.evaluate(context):
6568
output_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
6669
replacements = self.resolve_replacements(context)
6770
try:
68-
input_file = open(yaml_filename, 'r')
71+
input_file = open(yaml_filename)
6972
self.replace(input_file, output_file, replacements)
7073
except Exception as err: # noqa: B902
7174
print('ReplaceString substitution error: ', err)
@@ -76,15 +79,16 @@ def perform(self, context: launch.LaunchContext) -> Text:
7679
else:
7780
return yaml_filename
7881

79-
def resolve_replacements(self, context):
82+
def resolve_replacements(self, context: launch.LaunchContext) -> dict[str, str]:
8083
resolved_replacements = {}
8184
for key in self.__replacements:
8285
resolved_replacements[key] = launch.utilities.perform_substitutions(
8386
context, self.__replacements[key]
8487
)
8588
return resolved_replacements
8689

87-
def replace(self, input_file, output_file, replacements):
90+
def replace(self, input_file: launch.SomeSubstitutionsType,
91+
output_file: launch.SomeSubstitutionsType, replacements: dict[str, str]) -> None:
8892
for line in input_file:
8993
for key, value in replacements.items():
9094
if isinstance(key, str) and isinstance(value, str):

‎nav2_common/nav2_common/launch/rewritten_yaml.py‎

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,30 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from collections.abc import Generator
1516
import tempfile
16-
from typing import Dict, List, Optional, Text
17+
from typing import Optional, TypeAlias, Union
1718

1819
import launch
1920
import yaml
2021

22+
YamlValue: TypeAlias = Union[str, int, float, bool]
23+
2124

2225
class DictItemReference:
2326

24-
def __init__(self, dictionary, key):
27+
def __init__(self, dictionary: dict[str, YamlValue], key: str):
2528
self.dictionary = dictionary
2629
self.dictKey = key
2730

28-
def key(self):
31+
def key(self) -> str:
2932
return self.dictKey
3033

31-
def setValue(self, value):
34+
def setValue(self, value: YamlValue) -> None:
3235
self.dictionary[self.dictKey] = value
3336

3437

35-
class RewrittenYaml(launch.Substitution):
38+
class RewrittenYaml(launch.Substitution): # type: ignore[misc]
3639
"""
3740
Substitution that modifies the given YAML file.
3841
@@ -42,10 +45,10 @@ class RewrittenYaml(launch.Substitution):
4245
def __init__(
4346
self,
4447
source_file: launch.SomeSubstitutionsType,
45-
param_rewrites: Dict,
48+
param_rewrites: dict[str, launch.SomeSubstitutionsType],
4649
root_key: Optional[launch.SomeSubstitutionsType] = None,
47-
key_rewrites: Optional[Dict] = None,
48-
convert_types=False,
50+
key_rewrites: Optional[dict[str, launch.SomeSubstitutionsType]] = None,
51+
convert_types: bool = False,
4952
) -> None:
5053
super().__init__()
5154
"""
@@ -61,7 +64,8 @@ def __init__(
6164
# import here to avoid loop
6265
from launch.utilities import normalize_to_list_of_substitutions
6366

64-
self.__source_file = normalize_to_list_of_substitutions(source_file)
67+
self.__source_file: list[launch.Substitution] = \
68+
normalize_to_list_of_substitutions(source_file)
6569
self.__param_rewrites = {}
6670
self.__key_rewrites = {}
6771
self.__convert_types = convert_types
@@ -79,19 +83,19 @@ def __init__(
7983
self.__root_key = normalize_to_list_of_substitutions(root_key)
8084

8185
@property
82-
def name(self) -> List[launch.Substitution]:
86+
def name(self) -> list[launch.Substitution]:
8387
"""Getter for name."""
8488
return self.__source_file
8589

86-
def describe(self) -> Text:
90+
def describe(self) -> str:
8791
"""Return a description of this substitution as a string."""
8892
return ''
8993

90-
def perform(self, context: launch.LaunchContext) -> Text:
94+
def perform(self, context: launch.LaunchContext) -> str:
9195
yaml_filename = launch.utilities.perform_substitutions(context, self.name)
9296
rewritten_yaml = tempfile.NamedTemporaryFile(mode='w', delete=False)
9397
param_rewrites, keys_rewrites = self.resolve_rewrites(context)
94-
data = yaml.safe_load(open(yaml_filename, 'r'))
98+
data = yaml.safe_load(open(yaml_filename))
9599
self.substitute_params(data, param_rewrites)
96100
self.add_params(data, param_rewrites)
97101
self.substitute_keys(data, keys_rewrites)
@@ -103,7 +107,8 @@ def perform(self, context: launch.LaunchContext) -> Text:
103107
rewritten_yaml.close()
104108
return rewritten_yaml.name
105109

106-
def resolve_rewrites(self, context):
110+
def resolve_rewrites(self, context: launch.LaunchContext) -> \
111+
tuple[dict[str, str], dict[str, str]]:
107112
resolved_params = {}
108113
for key in self.__param_rewrites:
109114
resolved_params[key] = launch.utilities.perform_substitutions(
@@ -116,7 +121,8 @@ def resolve_rewrites(self, context):
116121
)
117122
return resolved_params, resolved_keys
118123

119-
def substitute_params(self, yaml, param_rewrites):
124+
def substitute_params(self, yaml: dict[str, YamlValue],
125+
param_rewrites: dict[str, str]) -> None:
120126
# substitute leaf-only parameters
121127
for key in self.getYamlLeafKeys(yaml):
122128
if key.key() in param_rewrites:
@@ -132,7 +138,8 @@ def substitute_params(self, yaml, param_rewrites):
132138
yaml_keys = path.split('.')
133139
yaml = self.updateYamlPathVals(yaml, yaml_keys, rewrite_val)
134140

135-
def add_params(self, yaml, param_rewrites):
141+
def add_params(self, yaml: dict[str, YamlValue],
142+
param_rewrites: dict[str, str]) -> None:
136143
# add new total path parameters
137144
yaml_paths = self.pathify(yaml)
138145
for path in param_rewrites:
@@ -142,7 +149,10 @@ def add_params(self, yaml, param_rewrites):
142149
if 'ros__parameters' in yaml_keys:
143150
yaml = self.updateYamlPathVals(yaml, yaml_keys, new_val)
144151

145-
def updateYamlPathVals(self, yaml, yaml_key_list, rewrite_val):
152+
def updateYamlPathVals(
153+
self, yaml: dict[str, YamlValue],
154+
yaml_key_list: list[str], rewrite_val: YamlValue) -> dict[str, YamlValue]:
155+
146156
for key in yaml_key_list:
147157
if key == yaml_key_list[-1]:
148158
yaml[key] = rewrite_val
@@ -153,12 +163,15 @@ def updateYamlPathVals(self, yaml, yaml_key_list, rewrite_val):
153163
yaml[int(key)], yaml_key_list, rewrite_val
154164
)
155165
else:
156-
yaml[key] = self.updateYamlPathVals(
157-
yaml.get(key, {}), yaml_key_list, rewrite_val
166+
yaml[key] = self.updateYamlPathVals( # type: ignore[assignment]
167+
yaml.get(key, {}), # type: ignore[arg-type]
168+
yaml_key_list,
169+
rewrite_val
158170
)
159171
return yaml
160172

161-
def substitute_keys(self, yaml, key_rewrites):
173+
def substitute_keys(
174+
self, yaml: dict[str, YamlValue], key_rewrites: dict[str, str]) -> None:
162175
if len(key_rewrites) != 0:
163176
for key in list(yaml.keys()):
164177
val = yaml[key]
@@ -169,20 +182,31 @@ def substitute_keys(self, yaml, key_rewrites):
169182
if isinstance(val, dict):
170183
self.substitute_keys(val, key_rewrites)
171184

172-
def getYamlLeafKeys(self, yamlData):
173-
try:
174-
for key in yamlData.keys():
175-
for k in self.getYamlLeafKeys(yamlData[key]):
176-
yield k
177-
yield DictItemReference(yamlData, key)
178-
except AttributeError:
185+
def getYamlLeafKeys(self, yamlData: dict[str, YamlValue]) -> \
186+
Generator[DictItemReference, None, None]:
187+
if not isinstance(yamlData, dict):
179188
return
180189

181-
def pathify(self, d, p=None, paths=None, joinchar='.'):
190+
for key in yamlData.keys():
191+
child = yamlData[key]
192+
193+
if isinstance(child, dict):
194+
# Recursively process nested dictionaries
195+
yield from self.getYamlLeafKeys(child)
196+
197+
yield DictItemReference(yamlData, key)
198+
199+
def pathify(
200+
self, d: Union[dict[str, YamlValue], list[YamlValue], YamlValue],
201+
p: Optional[str] = None,
202+
paths: Optional[dict[str, YamlValue]] = None,
203+
joinchar: str = '.') -> dict[str, YamlValue]:
182204
if p is None:
183205
paths = {}
184206
self.pathify(d, '', paths, joinchar=joinchar)
185207
return paths
208+
209+
assert paths is not None
186210
pn = p
187211
if p != '':
188212
pn += joinchar
@@ -195,8 +219,9 @@ def pathify(self, d, p=None, paths=None, joinchar='.'):
195219
self.pathify(e, pn + str(idx), paths, joinchar=joinchar)
196220
else:
197221
paths[p] = d
222+
return paths
198223

199-
def convert(self, text_value):
224+
def convert(self, text_value: str) -> YamlValue:
200225
if self.__convert_types:
201226
# try converting to int or float
202227
try:

‎tools/pyproject.toml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@ explicit_package_bases = true
1818
strict = true
1919

2020
[[tool.mypy.overrides]]
21-
module = ["matplotlib.*", "rtree.*"]
21+
module = [
22+
"matplotlib.*",
23+
"rtree.*",
24+
"launch.*",
25+
]
26+
2227
ignore_missing_imports = true

0 commit comments

Comments
 (0)