Beispiel #1
0
def launch_process(launch_service, process_action, proc_info, proc_output, **kwargs):
    """
    Launch and interact with a process.

    On context entering, start execution of a ``process_action`` using the given ``launch_service``
    and yield a `ProcessProxy` to that ``process_action``.
    On context exiting, shut the process down if it has not been terminated yet.
    All additional arguments are forwarded to `ProcessProxy` on construction.
    """
    ensure_argument_type(
        process_action, types=launch.actions.ExecuteProcess, argument_name='process_action'
    )

    launch_service.emit_event(
        event=launch.events.IncludeLaunchDescription(
            launch_description=launch.LaunchDescription([process_action])
        )
    )
    process_proxy = ProcessProxy(process_action, proc_info, proc_output, **kwargs)
    try:
        yield process_proxy
    finally:
        if not process_proxy.terminated:
            launch_service.emit_event(
                event=launch.events.process.ShutdownProcess(
                    process_matcher=launch.events.matches_action(process_action)
                )
            )
def normalize_parameters(parameters: SomeParameters) -> Parameters:
    """
    Normalize the types used to store parameters to substitution types.

    The passed parameters must be an iterable where each element is
    a path to a yaml file or a dict.
    The normalized parameters will have all paths converted to a list of :class:`Substitution`,
    and dictionaries normalized using :meth:`normalize_parameter_dict`.
    """
    if isinstance(parameters, str) or not isinstance(parameters, Sequence):
        raise TypeError(
            'Expecting list of parameters, got {}'.format(parameters))

    normalized_params = []  # type: List[Union[ParameterFile, ParametersDict]]
    for param in parameters:
        if isinstance(param, Mapping):
            normalized_params.append(normalize_parameter_dict(param))
        else:
            # It's a path, normalize to a list of substitutions
            if isinstance(param, pathlib.Path):
                param = str(param)
            ensure_argument_type(param, SomeSubstitutionsType_types_tuple,
                                 'parameters')
            normalized_params.append(
                tuple(normalize_to_list_of_substitutions(param)))
    return tuple(normalized_params)
Beispiel #3
0
    def __init__(self,
                 param_file: Union[FilePath, SomeSubstitutionsType],
                 *,
                 allow_substs: [bool, SomeSubstitutionsType] = False) -> None:
        """
        Construct a parameter file description.

        :param param_file: Path to a parameter file.
        :param allow_subst: Allow substitutions in the parameter file.
        """
        # In Python, __del__ is called even if the constructor throws an
        # exception.  It is possible for ensure_argument_type() below to
        # throw an exception and try to access these member variables
        # during cleanup, so make sure to initialize them here.
        self.__evaluated_param_file: Optional[Path] = None
        self.__created_tmp_file = False

        ensure_argument_type(
            param_file,
            SomeSubstitutionsType_types_tuple + (os.PathLike, bytes),
            'param_file', 'ParameterFile()')
        ensure_argument_type(allow_substs, bool, 'allow_subst',
                             'ParameterFile()')
        self.__param_file: Union[List[Substitution], FilePath] = param_file
        if isinstance(param_file, SomeSubstitutionsType_types_tuple):
            self.__param_file = normalize_to_list_of_substitutions(param_file)
        self.__allow_substs = normalize_typed_substitution(allow_substs,
                                                           data_type=bool)
        self.__evaluated_allow_substs: Optional[bool] = None
Beispiel #4
0
def test_invalid_argument_types_with_caller():
    """Test the ensure_argument_type function with invalid input and caller name."""
    with pytest.raises(TypeError) as ex:
        ensure_argument_type(1, [float, complex, list, tuple], 'test arg name',
                             'My test caller')
    assert 'test arg name' in str(ex.value)
    assert 'My test caller' in str(ex.value)
Beispiel #5
0
    def __init__(self,
                 name: SomeSubstitutionsType,
                 value: SomeValueType,
                 *,
                 value_type: Optional[AllowedTypesType] = None) -> None:
        """
        Construct a parameter description.

        :param name: Name of the parameter.
        :param value: Value of the parameter.
        :param value_type: Used when `value` is a substitution, to coerce the result.
            Can be one of:
                - A scalar type: `int`, `str`, `float`, `bool`.
                  `bool` are written like in `yaml`.
                  Both `1` and `1.` are valid floats.
                - An uniform list: `List[int]`, `List[str]`, `List[float]`, `List[bool]`.
                  Lists are written like in `yaml`.
                - `None`, which means that yaml rules will be used.
                  The result of the convertion must be one of the above types,
                  if not `ValueError` is raised.
            If value is not a substitution and this parameter is provided,
            it will be used to check `value` type.
        """
        ensure_argument_type(name, SomeSubstitutionsType_types_tuple, 'name')

        self.__name = normalize_to_list_of_substitutions(name)
        self.__parameter_value = ParameterValue(value, value_type=value_type)
        self.__evaluated_parameter_name: Optional[Text] = None
        self.__evaluated_parameter_rule: Optional[Tuple[
            Text, 'EvaluatedParameterValue']] = None
Beispiel #6
0
    def __init__(
        self,
        *,
        composable_node_descriptions: List[ComposableNode],
        target_container: Union[SomeSubstitutionsType, ComposableNodeContainer],
        **kwargs,
    ) -> None:
        """
        Construct a LoadComposableNodes action.

        The container node is expected to provide a `~/_container/load_node` service for
        loading purposes.
        Loading will be performed sequentially.
        When executed, this action will block until the container's load service is available.
        Make sure any LoadComposableNode action is executed only after its container processes
        has started.

        :param composable_node_descriptions: descriptions of composable nodes to be loaded
        :param target_container: the container to load the nodes into
        """
        ensure_argument_type(
            target_container,
            list(SomeSubstitutionsType_types_tuple) +
            [ComposableNodeContainer],
            'target_container',
            'LoadComposableNodes'
        )
        super().__init__(**kwargs)
        self.__composable_node_descriptions = composable_node_descriptions
        self.__target_container = target_container
        self.__final_target_container_name = None  # type: Optional[Text]
        self.__logger = launch.logging.get_logger(__name__)
Beispiel #7
0
 def expand_dict(input_dict):
     expanded_dict = {}
     for k, v in input_dict.items():
         # Key (parameter/group name) can only be a string/Substitutions that evaluates
         # to a string.
         expanded_key = perform_substitutions(
             context, normalize_to_list_of_substitutions(k))
         if isinstance(v, dict):
             # Expand the nested dict.
             expanded_value = expand_dict(v)
         elif isinstance(v, list):
             # Expand each element.
             expanded_value = []
             for e in v:
                 if isinstance(e, list):
                     raise TypeError(
                         'Nested lists are not supported for parameters: {} found in {}'
                         .format(e, v))
                 expanded_value.append(
                     perform_substitution_if_applicable(context, e))
         # Tuples are treated as Substitution(s) to be concatenated.
         elif isinstance(v, tuple):
             for e in v:
                 ensure_argument_type(
                     e, SomeSubstitutionsType_types_tuple,
                     'parameter dictionary tuple entry', 'Node')
             expanded_value = perform_substitutions(
                 context, normalize_to_list_of_substitutions(v))
         else:
             expanded_value = perform_substitution_if_applicable(
                 context, v)
         expanded_dict[expanded_key] = expanded_value
     return expanded_dict
def _normalize_parameter_array_value(
        value: SomeParameterValue) -> ParameterValue:
    """Normalize substitutions while preserving the type if it's not a substitution."""
    if not isinstance(value, Sequence):
        raise TypeError('Value {} must be a sequence'.format(repr(value)))

    # Figure out what type the list should be
    has_types = set()  # type: Set[type]
    for subvalue in value:
        allowed_subtypes = (float, int, str,
                            bool) + SomeSubstitutionsType_types_tuple
        ensure_argument_type(subvalue, allowed_subtypes, 'subvalue')

        if isinstance(subvalue, Substitution):
            has_types.add(Substitution)
        elif isinstance(subvalue, str):
            has_types.add(str)
        elif isinstance(subvalue, bool):
            has_types.add(bool)
        elif isinstance(subvalue, int):
            has_types.add(int)
        elif isinstance(subvalue, float):
            has_types.add(float)
        elif isinstance(subvalue, Sequence):
            has_types.add(tuple)
        else:
            raise RuntimeError('Failed to handle type {}'.format(
                repr(subvalue)))

    if {int} == has_types:
        # everything is an integer
        make_mypy_happy_int = cast(List[int], value)
        return tuple(int(e) for e in make_mypy_happy_int)
    elif has_types in ({float}, {int, float}):
        # all were floats or ints, so return floats
        make_mypy_happy_float = cast(List[Union[int, float]], value)
        return tuple(float(e) for e in make_mypy_happy_float)
    elif Substitution in has_types and has_types.issubset(
        {str, Substitution, tuple}):
        # make a list of substitutions forming a single string
        return tuple(
            normalize_to_list_of_substitutions(
                cast(SomeSubstitutionsType, value)))
    elif {bool} == has_types:
        # all where bools
        return tuple(bool(e) for e in value)
    else:
        # Should evaluate to a list of strings
        # Normalize to a list of lists of substitutions
        new_value = []  # type: List[SomeSubstitutionsType]
        for element in value:
            if isinstance(element, float) or isinstance(
                    element, int) or isinstance(element, bool):
                new_value.append(str(element))
            else:
                new_value.append(element)
        return tuple(normalize_to_list_of_substitutions(e) for e in new_value)
def normalize_remap_rule(remap_rule: SomeRemapRule) -> RemapRule:
    """Normalize a remap rule to a specific type."""
    ensure_argument_type(remap_rule, (tuple), 'remap_rule')
    if len(remap_rule) != 2:
        raise TypeError(
            'remap_rule must be a tuple of length 2, got length {}'.format(
                len(remap_rule)))
    from_sub = tuple(normalize_to_list_of_substitutions(remap_rule[0]))
    to_sub = tuple(normalize_to_list_of_substitutions(remap_rule[1]))
    return from_sub, to_sub
Beispiel #10
0
    def __init__(self, *, period: Union[float, SomeSubstitutionsType],
                 actions: Iterable[LaunchDescriptionEntity], **kwargs) -> None:
        """
        Create a RosTimer.

        :param period: is the time (in seconds) to set the timer for.
        :param actions: is an iterable containing actions to be executed upon on timeout.
        """
        super().__init__(period=period, actions=actions, **kwargs)
        period_types = list(SomeSubstitutionsType_types_tuple) + [float]
        ensure_argument_type(period, period_types, 'period', 'RosTimer')
        ensure_argument_type(actions, collections.abc.Iterable, 'actions',
                             'RosTimer')
        self.__period = type_utils.normalize_typed_substitution(period, float)
        self.__timer_future: Optional[asyncio.Future] = None
Beispiel #11
0
    def __init__(self,
                 process_action,
                 proc_info,
                 proc_output,
                 *,
                 output_filter=None):
        """
        Construct a proxy for the given ``process_action``.

        :param process_action: `launch.actions.ExecuteProcess` instance to proxy.
        :param proc_info: `ActiveProcInfoHandler` tracking process state.
        :param proc_output: `ActiveIoHandler` tracking process output.
        :param output_filter: an optional callable to filter output text.
        """
        ensure_argument_type(process_action,
                             types=launch.actions.ExecuteProcess,
                             argument_name='process_action')
        ensure_argument_type(proc_info,
                             types=ActiveProcInfoHandler,
                             argument_name='proc_info')
        ensure_argument_type(proc_output,
                             types=ActiveIoHandler,
                             argument_name='proc_output')
        if output_filter is not None and not callable(output_filter):
            raise TypeError(
                "Expected 'output_filter' to be callable but got '{!r}'".
                format(output_filter))
        self._process_action = process_action
        self._proc_info = proc_info
        self._proc_output = proc_output
        self._output_filter = output_filter
Beispiel #12
0
def evaluate_parameter_dict(
    context: LaunchContext,
    parameters: ParametersDict
) -> Dict[str, EvaluatedParameterValue]:
    if not isinstance(parameters, Mapping):
        raise TypeError('expected dict')
    output_dict = {}  # type: Dict[str, EvaluatedParameterValue]
    for name, value in parameters.items():
        if not isinstance(name, tuple):
            raise TypeError('Expecting tuple of substitutions got {}'.format(repr(name)))
        evaluated_name = perform_substitutions(context, list(name))  # type: str
        evaluated_value = None  # type: Optional[EvaluatedParameterValue]

        if isinstance(value, tuple) and len(value):
            if isinstance(value[0], Substitution):
                # Value is a list of substitutions, so perform them to make a string
                evaluated_value = perform_substitutions(context, list(value))
            elif isinstance(value[0], Sequence):
                # Value is an array of a list of substitutions
                output_subvalue = []  # List[str]
                for subvalue in value:
                    output_subvalue.append(perform_substitutions(context, list(subvalue)))
                    evaluated_value = tuple(output_subvalue)
            else:
                # Value is an array of the same type, so nothing to evaluate.
                output_value = []
                target_type = type(value[0])
                for i, subvalue in enumerate(value):
                    output_value.append(target_type(subvalue))
                    evaluated_value = tuple(output_value)
        else:
            # Value is a singular type, so nothing to evaluate
            ensure_argument_type(value, (float, int, str, bool, bytes), 'value')
            evaluated_value = cast(Union[float, int, str, bool, bytes], value)
        if evaluated_value is None:
            raise TypeError('given unnormalized parameters %r, %r' % (name, value))
        output_dict[evaluated_name] = evaluated_value
    return output_dict
    def __init__(self,
                 param_file: Union[FilePath, SomeSubstitutionsType],
                 *,
                 allow_substs: [bool, SomeSubstitutionsType] = False) -> None:
        """
        Construct a parameter file description.

        :param param_file: Path to a parameter file.
        :param allow_subst: Allow substitutions in the parameter file.
        """
        ensure_argument_type(
            param_file,
            SomeSubstitutionsType_types_tuple + (os.PathLike, bytes),
            'param_file', 'ParameterFile()')
        ensure_argument_type(allow_substs, bool, 'allow_subst',
                             'ParameterFile()')
        self.__param_file: Union[List[Substitution], FilePath] = param_file
        if isinstance(param_file, SomeSubstitutionsType_types_tuple):
            self.__param_file = normalize_to_list_of_substitutions(param_file)
        self.__allow_substs = normalize_typed_substitution(allow_substs,
                                                           data_type=bool)
        self.__evaluated_allow_substs: Optional[bool] = None
        self.__evaluated_param_file: Optional[Path] = None
        self.__created_tmp_file = False
Beispiel #14
0
def test_invalid_argument_types():
    """Test the ensure_argument_type function with invalid input."""
    with pytest.raises(TypeError):
        ensure_argument_type(None, None, 'none')
    with pytest.raises(TypeError):
        ensure_argument_type(1, str, 'foo')
    with pytest.raises(TypeError):
        ensure_argument_type('bar', [int, float, list, tuple], 'arg_bar')

    class MockClass:
        pass

    class MockChildClass(MockClass):
        pass

    mock_class_obj = MockClass()
    with pytest.raises(TypeError):
        ensure_argument_type(mock_class_obj, MockChildClass, 'MockChildClass')
Beispiel #15
0
    def __init__(self,
                 *,
                 package: SomeSubstitutionsType,
                 node_executable: SomeSubstitutionsType,
                 node_name: Optional[SomeSubstitutionsType] = None,
                 node_namespace: SomeSubstitutionsType = '',
                 parameters: Optional[SomeParameters] = None,
                 remappings: Optional[SomeRemapRules] = None,
                 arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
                 **kwargs) -> None:
        """
        Construct an Node action.

        Many arguments are passed eventually to
        :class:`launch.actions.ExecuteProcess`, so see the documentation of
        that class for additional details.
        However, the `cmd` is not meant to be used, instead use the
        `node_executable` and `arguments` keyword arguments to this function.

        This action, once executed, delegates most work to the
        :class:`launch.actions.ExecuteProcess`, but it also converts some ROS
        specific arguments into generic command line arguments.

        The launch_ros.substitutions.ExecutableInPackage substitution is used
        to find the executable at runtime, so this Action also raise the
        exceptions that substituion can raise when the package or executable
        are not found.

        If the node_name is not given (or is None) then no name is passed to
        the node on creation and instead the default name specified within the
        code of the node is used instead.

        The node_namespace can either be absolute (i.e. starts with /) or
        relative.
        If absolute, then nothing else is considered and this is passed
        directly to the node to set the namespace.
        If relative, the namespace in the 'ros_namespace' LaunchConfiguration
        will be prepended to the given relative node namespace.
        If no node_namespace is given, then the default namespace `/` is
        assumed.

        The parameters are passed as a list, with each element either a yaml
        file that contains parameter rules (string or pathlib.Path to the full
        path of the file), or a dictionary that specifies parameter rules.
        Keys of the dictionary can be strings or an iterable of Substitutions
        that will be expanded to a string.
        Values in the dictionary can be strings, integers, floats, or tuples
        of Substitutions that will be expanded to a string.
        Additionally, values in the dictionary can be lists of the
        aforementioned types, or another dictionary with the same properties.
        A yaml file with the resulting parameters from the dictionary will be
        written to a temporary file, the path to which will be passed to the
        node.
        Multiple dictionaries/files can be passed: each file path will be
        passed in in order to the node (where the last definition of a
        parameter takes effect).

        :param: package the package in which the node executable can be found
        :param: node_executable the name of the executable to find
        :param: node_name the name of the node
        :param: node_namespace the ros namespace for this Node
        :param: parameters list of names of yaml files with parameter rules,
            or dictionaries of parameters.
        :param: remappings ordered list of 'to' and 'from' string pairs to be
            passed to the node as ROS remapping rules
        :param: arguments list of extra arguments for the node
        """
        cmd = [
            ExecutableInPackage(package=package, executable=node_executable)
        ]
        cmd += [] if arguments is None else arguments
        # Reserve space for ros specific arguments.
        # The substitutions will get expanded when the action is executed.
        cmd += ['--ros-args'
                ]  # Prepend ros specific arguments with --ros-args flag
        if node_name is not None:
            cmd += [
                '-r',
                LocalSubstitution("ros_specific_arguments['name']",
                                  description='node name')
            ]
        if parameters is not None:
            ensure_argument_type(parameters, (list), 'parameters', 'Node')
            # All elements in the list are paths to files with parameters (or substitutions that
            # evaluate to paths), or dictionaries of parameters (fields can be substitutions).
            i = 0
            for param in parameters:
                cmd += [
                    LocalSubstitution(
                        "ros_specific_arguments['params'][{}]".format(i),
                        description='parameter {}'.format(i))
                ]
                i += 1
            normalized_params = normalize_parameters(parameters)
        if remappings is not None:
            i = 0
            for remapping in normalize_remap_rules(remappings):
                k, v = remapping
                cmd += [
                    '-r',
                    LocalSubstitution(
                        "ros_specific_arguments['remaps'][{}]".format(i),
                        description='remapping {}'.format(i))
                ]
                i += 1
        super().__init__(cmd=cmd, **kwargs)
        self.__package = package
        self.__node_executable = node_executable
        self.__node_name = node_name
        self.__node_namespace = node_namespace
        self.__parameters = [] if parameters is None else normalized_params
        self.__remappings = [] if remappings is None else remappings
        self.__arguments = arguments

        self.__expanded_node_name = '<node_name_unspecified>'
        self.__expanded_node_namespace = ''
        self.__final_node_name = None  # type: Optional[Text]
        self.__expanded_parameter_files = None  # type: Optional[List[Text]]
        self.__expanded_remappings = None  # type: Optional[List[Tuple[Text, Text]]]

        self.__substitutions_performed = False

        self.__logger = launch.logging.get_logger(__name__)
def evaluate_parameters(context: LaunchContext,
                        parameters: Parameters) -> EvaluatedParameters:
    """
    Evaluate substitutions to produce paths and name/value pairs.

    The parameters must have been normalized with normalize_parameters() prior to calling this.

    :param parameters: normalized parameters
    :returns: values after evaluating lists of substitutions
    """
    output_params = [
    ]  # type: List[Union[pathlib.Path, Dict[str, EvaluatedParameterValue]]]
    for param in parameters:
        # If it's a list of substitutions then evaluate them to a string and return a pathlib.Path
        if isinstance(param, tuple) and len(param) and isinstance(
                param[0], Substitution):
            # Evaluate a list of Substitution to a file path
            output_params.append(
                pathlib.Path(perform_substitutions(context, list(param))))
        elif isinstance(param, Mapping):
            # It's a list of name/value pairs
            output_dict = {}  # type: Dict[str, EvaluatedParameterValue]
            for name, value in param.items():
                if not isinstance(name, tuple):
                    raise TypeError(
                        'Expecting tuple of substitutions got {}'.format(
                            repr(name)))
                evaluated_name = perform_substitutions(context,
                                                       list(name))  # type: str
                evaluated_value = None  # type: Optional[EvaluatedParameterValue]

                if isinstance(value, tuple) and len(value):
                    if isinstance(value[0], Substitution):
                        # Value is a list of substitutions, so perform them to make a string
                        evaluated_value = perform_substitutions(
                            context, list(value))
                    elif isinstance(value[0], Sequence):
                        # Value is an array of a list of substitutions
                        output_subvalue = []  # List[str]
                        for subvalue in value:
                            output_subvalue.append(
                                perform_substitutions(context, list(subvalue)))
                        evaluated_value = tuple(output_subvalue)
                    else:
                        # Value is an array of the same type, so nothing to evaluate.
                        output_value = []
                        target_type = type(value[0])
                        for i, subvalue in enumerate(value):
                            output_value.append(target_type(subvalue))
                        evaluated_value = tuple(output_value)
                else:
                    # Value is a singular type, so nothing to evaluate
                    ensure_argument_type(value, (float, int, str, bool, bytes),
                                         'value')
                    evaluated_value = cast(Union[float, int, str, bool, bytes],
                                           value)
                if evaluated_value is None:
                    raise TypeError('given unnormalized parameters %r, %r' %
                                    (name, value))
                output_dict[evaluated_name] = evaluated_value
            output_params.append(output_dict)
    return tuple(output_params)
Beispiel #17
0
def test_valid_argument_types():
    """Test the ensure_argument_type function with valid input."""
    ensure_argument_type('foo', str, 'arg_foo')
    ensure_argument_type(1, [str, int], 'arg_bar')
    ensure_argument_type([3.14159], [list, str, int], '')
    ensure_argument_type(3.14159, [float], ' ')

    class MockClass:
        pass

    mock_class_obj = MockClass()
    ensure_argument_type(mock_class_obj, MockClass, 'MockClass')

    # With inheritence
    class MockChildClass(MockClass):
        pass

    mock_child_obj = MockChildClass()
    ensure_argument_type(mock_child_obj, MockClass, 'MockChildClass')
Beispiel #18
0
def evaluate_parameter_dict(
        context: LaunchContext,
        parameters: ParametersDict) -> Dict[str, EvaluatedParameterValue]:
    def check_sequence_type_is_allowed(sequence):
        # Check if the items of the sequence aren't dissimilar.
        # Also, check if the item type is one of bool, int, float, str.
        if not sequence:  # Don't allow empty sequences
            return False
        subtype = type(sequence[0])
        if subtype not in (bool, int, float, str):
            return False
        for item in sequence:
            if not isinstance(item, subtype):
                return False
        return True

    if not isinstance(parameters, Mapping):
        raise TypeError('expected dict')
    output_dict: Dict[str, EvaluatedParameterValue] = {}
    for name, value in parameters.items():
        if not isinstance(name, tuple):
            raise TypeError('Expecting tuple of substitutions got {}'.format(
                repr(name)))
        evaluated_name: str = perform_substitutions(context, list(name))
        evaluated_value: Optional[EvaluatedParameterValue] = None

        if isinstance(value, tuple) and len(value):
            if isinstance(value[0], Substitution):
                # Value is a list of substitutions, so perform them to make a string
                evaluated_value = perform_substitutions(context, list(value))
                yaml_evaluated_value = yaml.safe_load(evaluated_value)
                if yaml_evaluated_value is None:
                    yaml_evaluated_value = ''
                if type(yaml_evaluated_value) in (bool, int, float, str,
                                                  bytes):
                    evaluated_value = yaml_evaluated_value
                elif isinstance(yaml_evaluated_value, Sequence):
                    # str and bytes were already handled in the previous case
                    # If it is a list with dissimilar types, don't evaluate the value as yaml.
                    if not check_sequence_type_is_allowed(
                            yaml_evaluated_value):
                        raise TypeError(
                            'Expected a non-empty sequence, with items of uniform type. '
                            'Allowed sequence item types are bool, int, float, str.'
                        )
                    evaluated_value = tuple(yaml_evaluated_value)
                else:
                    raise TypeError(
                        'Allowed value types are bytes, bool, int, float, str, Sequence[bool]'
                        ', Sequence[int], Sequence[float], Sequence[str]. Got {}.'
                        .format(type(yaml_evaluated_value)))
            elif isinstance(value[0], Sequence):
                # Value is an array of a list of substitutions
                output_subvalue: List[str] = []
                for subvalue in value:
                    value = perform_substitutions(context, list(subvalue))
                    output_subvalue.append(value)
                evaluated_value = tuple(output_subvalue)
                # All values in a list must have the same type.
                # If they don't then assume it is a list of strings
                yaml_evaluated_value = [
                    yaml.safe_load(item) for item in evaluated_value
                ]
                if not check_sequence_type_is_allowed(yaml_evaluated_value):
                    raise TypeError(
                        'Expected a non-empty sequence, with items of uniform type. '
                        'Allowed sequence item types are bool, int, float, str.'
                    )
                evaluated_value = tuple(yaml_evaluated_value)
            else:
                # Value is an array of the same type, so nothing to evaluate.
                output_value = []
                target_type = type(value[0])
                for i, subvalue in enumerate(value):
                    output_value.append(target_type(subvalue))
                    evaluated_value = tuple(output_value)
        else:
            # Value is a singular type, so nothing to evaluate
            ensure_argument_type(value, (float, int, str, bool, bytes),
                                 'value')
            evaluated_value = cast(Union[float, int, str, bool, bytes], value)
        if evaluated_value is None:
            raise TypeError('given unnormalized parameters %r, %r' %
                            (name, value))
        output_dict[evaluated_name] = evaluated_value
    return output_dict
Beispiel #19
0
    def __init__(self,
                 *,
                 executable: Optional[SomeSubstitutionsType] = None,
                 node_executable: Optional[SomeSubstitutionsType] = None,
                 package: Optional[SomeSubstitutionsType] = None,
                 name: Optional[SomeSubstitutionsType] = None,
                 namespace: Optional[SomeSubstitutionsType] = None,
                 node_name: Optional[SomeSubstitutionsType] = None,
                 node_namespace: SomeSubstitutionsType = None,
                 exec_name: Optional[SomeSubstitutionsType] = None,
                 parameters: Optional[SomeParameters] = None,
                 remappings: Optional[SomeRemapRules] = None,
                 arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
                 **kwargs) -> None:
        """
        Construct an Node action.

        Many arguments are passed eventually to
        :class:`launch.actions.ExecuteProcess`, so see the documentation of
        that class for additional details.
        However, the `cmd` is not meant to be used, instead use the
        `executable` and `arguments` keyword arguments to this function.

        This action, once executed, delegates most work to the
        :class:`launch.actions.ExecuteProcess`, but it also converts some ROS
        specific arguments into generic command line arguments.

        The launch_ros.substitutions.ExecutableInPackage substitution is used
        to find the executable at runtime, so this Action also raise the
        exceptions that substituion can raise when the package or executable
        are not found.

        If the name is not given (or is None) then no name is passed to
        the node on creation and instead the default name specified within the
        code of the node is used instead.

        The namespace can either be absolute (i.e. starts with /) or
        relative.
        If absolute, then nothing else is considered and this is passed
        directly to the node to set the namespace.
        If relative, the namespace in the 'ros_namespace' LaunchConfiguration
        will be prepended to the given relative node namespace.
        If no namespace is given, then the default namespace `/` is
        assumed.

        The parameters are passed as a list, with each element either a yaml
        file that contains parameter rules (string or pathlib.Path to the full
        path of the file), or a dictionary that specifies parameter rules.
        Keys of the dictionary can be strings or an iterable of Substitutions
        that will be expanded to a string.
        Values in the dictionary can be strings, integers, floats, or tuples
        of Substitutions that will be expanded to a string.
        Additionally, values in the dictionary can be lists of the
        aforementioned types, or another dictionary with the same properties.
        A yaml file with the resulting parameters from the dictionary will be
        written to a temporary file, the path to which will be passed to the
        node.
        Multiple dictionaries/files can be passed: each file path will be
        passed in in order to the node (where the last definition of a
        parameter takes effect).

        .. deprecated:: Foxy
           Parameters `node_executable`, `node_name`, and `node_namespace` are deprecated.
           Use `executable`, `name`, and `namespace` instead.

        :param: executable the name of the executable to find if a package
            is provided or otherwise a path to the executable to run.
        :param: node_executable (DEPRECATED) the name of the executable to find if a package
            is provided or otherwise a path to the executable to run.
        :param: package the package in which the node executable can be found
        :param: name the name of the node
        :param: namespace the ROS namespace for this Node
        :param: exec_name the label used to represent the process.
            Defaults to the basename of node executable.
        :param: node_name (DEPRECATED) the name of the node
        :param: node_namespace (DEPRECATED) the ros namespace for this Node
        :param: parameters list of names of yaml files with parameter rules,
            or dictionaries of parameters.
        :param: remappings ordered list of 'to' and 'from' string pairs to be
            passed to the node as ROS remapping rules
        :param: arguments list of extra arguments for the node
        """
        if node_executable is not None:
            warnings.warn(
                "The parameter 'node_executable' is deprecated, use 'executable' instead",
                stacklevel=2)
            if executable is not None:
                raise RuntimeError(
                    "Passing both 'node_executable' and 'executable' parameters. "
                    "Only use 'executable'")
            executable = node_executable

        if package is not None:
            cmd = [ExecutableInPackage(package=package, executable=executable)]
        else:
            cmd = [executable]
        cmd += [] if arguments is None else arguments
        # Reserve space for ros specific arguments.
        # The substitutions will get expanded when the action is executed.
        cmd += ['--ros-args'
                ]  # Prepend ros specific arguments with --ros-args flag
        if node_name is not None:
            warnings.warn(
                "The parameter 'node_name' is deprecated, use 'name' instead",
                stacklevel=2)
            if name is not None:
                raise RuntimeError(
                    "Passing both 'node_name' and 'name' parameters. Only use 'name'."
                )
            cmd += [
                '-r',
                LocalSubstitution("ros_specific_arguments['name']",
                                  description='node name')
            ]
            name = node_name
        if name is not None:
            cmd += [
                '-r',
                LocalSubstitution("ros_specific_arguments['name']",
                                  description='node name')
            ]
        if node_namespace:
            warnings.warn(
                "The parameter 'node_namespace' is deprecated, use 'namespace' instead"
            )
            if namespace:
                raise RuntimeError(
                    "Passing both 'node_namespace' and 'namespace' parameters. "
                    "Only use 'namespace'.")
            namespace = node_namespace
        if parameters is not None:
            ensure_argument_type(parameters, (list), 'parameters', 'Node')
            # All elements in the list are paths to files with parameters (or substitutions that
            # evaluate to paths), or dictionaries of parameters (fields can be substitutions).
            normalized_params = normalize_parameters(parameters)
        # Forward 'exec_name' as to ExecuteProcess constructor
        kwargs['name'] = exec_name
        super().__init__(cmd=cmd, **kwargs)
        self.__package = package
        self.__node_executable = executable
        self.__node_name = name
        self.__node_namespace = namespace
        self.__parameters = [] if parameters is None else normalized_params
        self.__remappings = [] if remappings is None else list(
            normalize_remap_rules(remappings))
        self.__arguments = arguments

        self.__expanded_node_name = self.UNSPECIFIED_NODE_NAME
        self.__expanded_node_namespace = self.UNSPECIFIED_NODE_NAMESPACE
        self.__expanded_parameter_arguments = None  # type: Optional[List[Tuple[Text, bool]]]
        self.__final_node_name = None  # type: Optional[Text]
        self.__expanded_remappings = None  # type: Optional[List[Tuple[Text, Text]]]

        self.__substitutions_performed = False

        self.__logger = launch.logging.get_logger(__name__)