From 0331a2e441545d8fead1bb6a9590887eb01317f8 Mon Sep 17 00:00:00 2001 From: Kenji Miyake <31987104+kenji-miyake@users.noreply.github.com> Date: Tue, 1 Mar 2022 07:17:03 +0900 Subject: [PATCH] Add parameter substitution (#297) * Add Parameter substitution Signed-off-by: Kenji Miyake * Fix bug of set_parameter.py Signed-off-by: Kenji Miyake * Add a test for SubstitutionFailure Signed-off-by: Kenji Miyake --- .../launch_ros/actions/set_parameter.py | 4 +- .../launch_ros/substitutions/__init__.py | 2 + .../launch_ros/substitutions/parameter.py | 72 +++++++++++++++++++ .../test_parameter_substitution_frontend.py | 67 +++++++++++++++++ .../test_parameter_substitution.py | 32 +++++++++ 5 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 launch_ros/launch_ros/substitutions/parameter.py create mode 100644 test_launch_ros/test/test_launch_ros/frontend/test_parameter_substitution_frontend.py create mode 100644 test_launch_ros/test/test_launch_ros/substitutions/test_parameter_substitution.py diff --git a/launch_ros/launch_ros/actions/set_parameter.py b/launch_ros/launch_ros/actions/set_parameter.py index abd66ff2d..1f3fe3ce6 100644 --- a/launch_ros/launch_ros/actions/set_parameter.py +++ b/launch_ros/launch_ros/actions/set_parameter.py @@ -76,12 +76,12 @@ def parse(cls, entity: Entity, parser: Parser): @property def name(self) -> ParameterName: """Getter for name.""" - return self.__param_dict.keys()[0] + return list(self.__param_dict.keys())[0] @property def value(self) -> ParameterValue: """Getter for value.""" - return self.__param_dict.values()[0] + return list(self.__param_dict.values())[0] def execute(self, context: LaunchContext): """Execute the action.""" diff --git a/launch_ros/launch_ros/substitutions/__init__.py b/launch_ros/launch_ros/substitutions/__init__.py index ed4d05d7c..c3a2d1331 100644 --- a/launch_ros/launch_ros/substitutions/__init__.py +++ b/launch_ros/launch_ros/substitutions/__init__.py @@ -18,6 +18,7 @@ from .find_package import FindPackage from .find_package import FindPackagePrefix from .find_package import FindPackageShare +from .parameter import Parameter __all__ = [ @@ -25,4 +26,5 @@ 'FindPackage', 'FindPackagePrefix', 'FindPackageShare', + 'Parameter', ] diff --git a/launch_ros/launch_ros/substitutions/parameter.py b/launch_ros/launch_ros/substitutions/parameter.py new file mode 100644 index 000000000..2d84153bc --- /dev/null +++ b/launch_ros/launch_ros/substitutions/parameter.py @@ -0,0 +1,72 @@ +# Copyright 2022 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. + +"""Module for the Parameter substitution.""" + +from typing import Iterable +from typing import List +from typing import Text + +from launch.frontend import expose_substitution +from launch.launch_context import LaunchContext +from launch.some_substitutions_type import SomeSubstitutionsType +from launch.substitution import Substitution +from launch.substitutions.substitution_failure import SubstitutionFailure +from launch.utilities import normalize_to_list_of_substitutions +from launch.utilities import perform_substitutions + + +@expose_substitution('param') +class Parameter(Substitution): + """ + Substitution that tries to get a parameter that was set by SetParameter. + + :raise: SubstitutionFailure when param is not found + """ + + def __init__( + self, + name: SomeSubstitutionsType, + ) -> None: + """Create a Parameter substitution.""" + super().__init__() + self.__name = normalize_to_list_of_substitutions(name) + + @classmethod + def parse(cls, data: Iterable[SomeSubstitutionsType]): + """Parse a Parameter substitution.""" + if not data or len(data) != 1: + raise AttributeError('param substitutions expect 1 argument') + kwargs = {'name': data[0]} + return cls, kwargs + + @property + def name(self) -> List[Substitution]: + """Getter for name.""" + return self.__name + + def describe(self) -> Text: + """Return a description of this substitution as a string.""" + name_str = ' + '.join([sub.describe() for sub in self.name]) + return '{}(name={})'.format(self.__class__.__name__, name_str) + + def perform(self, context: LaunchContext) -> Text: + """Perform the substitution.""" + name = perform_substitutions(context, self.name) + params_container = context.launch_configurations.get('global_params', None) + for param in params_container: + if isinstance(param, tuple): + if param[0] == name: + return param[1] + raise SubstitutionFailure("parameter '{}' not found".format(name)) diff --git a/test_launch_ros/test/test_launch_ros/frontend/test_parameter_substitution_frontend.py b/test_launch_ros/test/test_launch_ros/frontend/test_parameter_substitution_frontend.py new file mode 100644 index 000000000..7a30f8f04 --- /dev/null +++ b/test_launch_ros/test/test_launch_ros/frontend/test_parameter_substitution_frontend.py @@ -0,0 +1,67 @@ +# Copyright 2022 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. + +import io +import textwrap + +from launch import LaunchService +from launch.frontend import Parser +from launch.utilities import perform_substitutions + + +def test_parameter_substitution_yaml(): + yaml_file = textwrap.dedent( + r""" + launch: + - set_parameter: + name: name + value: value + + - let: + name: result + value: $(param name) + """ + ) + with io.StringIO(yaml_file) as f: + check_parameter_substitution(f) + + +def test_parameter_substitution_xml(): + xml_file = textwrap.dedent( + r""" + + + + + """ + ) + with io.StringIO(xml_file) as f: + check_parameter_substitution(f) + + +def check_parameter_substitution(file): + root_entity, parser = Parser.load(file) + ld = parser.parse_description(root_entity) + ls = LaunchService() + ls.include_launch_description(ld) + assert 0 == ls.run() + + def perform(substitution): + return perform_substitutions(ls.context, substitution) + + set_parameter, let = ld.describe_sub_entities() + assert perform(set_parameter.name) == 'name' + assert perform(set_parameter.value) == 'value' + assert perform(let.name) == 'result' + assert perform(let.value) == 'value' diff --git a/test_launch_ros/test/test_launch_ros/substitutions/test_parameter_substitution.py b/test_launch_ros/test/test_launch_ros/substitutions/test_parameter_substitution.py new file mode 100644 index 000000000..522f04213 --- /dev/null +++ b/test_launch_ros/test/test_launch_ros/substitutions/test_parameter_substitution.py @@ -0,0 +1,32 @@ +# Copyright 2022 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. + +"""Test for the Parameter substitutions.""" + +from launch import LaunchContext + +from launch.substitutions.substitution_failure import SubstitutionFailure + +from launch_ros.actions import SetParameter +from launch_ros.substitutions import Parameter + +import pytest + + +def test_parameter_substitution(): + context = LaunchContext() + SetParameter('name', 'value').execute(context) + assert Parameter('name').perform(context) == 'value' + with pytest.raises(SubstitutionFailure): + Parameter('name-invalid').perform(context)