Skip to content

Commit 0331a2e

Browse files
authored
Add parameter substitution (#297)
* Add Parameter substitution Signed-off-by: Kenji Miyake <[email protected]> * Fix bug of set_parameter.py Signed-off-by: Kenji Miyake <[email protected]> * Add a test for SubstitutionFailure Signed-off-by: Kenji Miyake <[email protected]>
1 parent 835b40c commit 0331a2e

File tree

5 files changed

+175
-2
lines changed

5 files changed

+175
-2
lines changed

launch_ros/launch_ros/actions/set_parameter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ def parse(cls, entity: Entity, parser: Parser):
7676
@property
7777
def name(self) -> ParameterName:
7878
"""Getter for name."""
79-
return self.__param_dict.keys()[0]
79+
return list(self.__param_dict.keys())[0]
8080

8181
@property
8282
def value(self) -> ParameterValue:
8383
"""Getter for value."""
84-
return self.__param_dict.values()[0]
84+
return list(self.__param_dict.values())[0]
8585

8686
def execute(self, context: LaunchContext):
8787
"""Execute the action."""

launch_ros/launch_ros/substitutions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
from .find_package import FindPackage
1919
from .find_package import FindPackagePrefix
2020
from .find_package import FindPackageShare
21+
from .parameter import Parameter
2122

2223

2324
__all__ = [
2425
'ExecutableInPackage',
2526
'FindPackage',
2627
'FindPackagePrefix',
2728
'FindPackageShare',
29+
'Parameter',
2830
]
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright 2022 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Module for the Parameter substitution."""
16+
17+
from typing import Iterable
18+
from typing import List
19+
from typing import Text
20+
21+
from launch.frontend import expose_substitution
22+
from launch.launch_context import LaunchContext
23+
from launch.some_substitutions_type import SomeSubstitutionsType
24+
from launch.substitution import Substitution
25+
from launch.substitutions.substitution_failure import SubstitutionFailure
26+
from launch.utilities import normalize_to_list_of_substitutions
27+
from launch.utilities import perform_substitutions
28+
29+
30+
@expose_substitution('param')
31+
class Parameter(Substitution):
32+
"""
33+
Substitution that tries to get a parameter that was set by SetParameter.
34+
35+
:raise: SubstitutionFailure when param is not found
36+
"""
37+
38+
def __init__(
39+
self,
40+
name: SomeSubstitutionsType,
41+
) -> None:
42+
"""Create a Parameter substitution."""
43+
super().__init__()
44+
self.__name = normalize_to_list_of_substitutions(name)
45+
46+
@classmethod
47+
def parse(cls, data: Iterable[SomeSubstitutionsType]):
48+
"""Parse a Parameter substitution."""
49+
if not data or len(data) != 1:
50+
raise AttributeError('param substitutions expect 1 argument')
51+
kwargs = {'name': data[0]}
52+
return cls, kwargs
53+
54+
@property
55+
def name(self) -> List[Substitution]:
56+
"""Getter for name."""
57+
return self.__name
58+
59+
def describe(self) -> Text:
60+
"""Return a description of this substitution as a string."""
61+
name_str = ' + '.join([sub.describe() for sub in self.name])
62+
return '{}(name={})'.format(self.__class__.__name__, name_str)
63+
64+
def perform(self, context: LaunchContext) -> Text:
65+
"""Perform the substitution."""
66+
name = perform_substitutions(context, self.name)
67+
params_container = context.launch_configurations.get('global_params', None)
68+
for param in params_container:
69+
if isinstance(param, tuple):
70+
if param[0] == name:
71+
return param[1]
72+
raise SubstitutionFailure("parameter '{}' not found".format(name))
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2022 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import io
16+
import textwrap
17+
18+
from launch import LaunchService
19+
from launch.frontend import Parser
20+
from launch.utilities import perform_substitutions
21+
22+
23+
def test_parameter_substitution_yaml():
24+
yaml_file = textwrap.dedent(
25+
r"""
26+
launch:
27+
- set_parameter:
28+
name: name
29+
value: value
30+
31+
- let:
32+
name: result
33+
value: $(param name)
34+
"""
35+
)
36+
with io.StringIO(yaml_file) as f:
37+
check_parameter_substitution(f)
38+
39+
40+
def test_parameter_substitution_xml():
41+
xml_file = textwrap.dedent(
42+
r"""
43+
<launch>
44+
<set_parameter name="name" value="value" />
45+
<let name="result" value="$(param name)" />
46+
</launch>
47+
"""
48+
)
49+
with io.StringIO(xml_file) as f:
50+
check_parameter_substitution(f)
51+
52+
53+
def check_parameter_substitution(file):
54+
root_entity, parser = Parser.load(file)
55+
ld = parser.parse_description(root_entity)
56+
ls = LaunchService()
57+
ls.include_launch_description(ld)
58+
assert 0 == ls.run()
59+
60+
def perform(substitution):
61+
return perform_substitutions(ls.context, substitution)
62+
63+
set_parameter, let = ld.describe_sub_entities()
64+
assert perform(set_parameter.name) == 'name'
65+
assert perform(set_parameter.value) == 'value'
66+
assert perform(let.name) == 'result'
67+
assert perform(let.value) == 'value'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2022 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test for the Parameter substitutions."""
16+
17+
from launch import LaunchContext
18+
19+
from launch.substitutions.substitution_failure import SubstitutionFailure
20+
21+
from launch_ros.actions import SetParameter
22+
from launch_ros.substitutions import Parameter
23+
24+
import pytest
25+
26+
27+
def test_parameter_substitution():
28+
context = LaunchContext()
29+
SetParameter('name', 'value').execute(context)
30+
assert Parameter('name').perform(context) == 'value'
31+
with pytest.raises(SubstitutionFailure):
32+
Parameter('name-invalid').perform(context)

0 commit comments

Comments
 (0)