diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 976aee32..f10ae370 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -22,6 +22,7 @@ from rosidl_parser.definition import FLOATING_POINT_TYPES from rosidl_parser.definition import INTEGER_TYPES from rosidl_parser.definition import NamespacedType from rosidl_parser.definition import SIGNED_INTEGER_TYPES +from rosidl_parser.definition import UnboundedSequence from rosidl_parser.definition import UNSIGNED_INTEGER_TYPES }@ @#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -30,6 +31,9 @@ from rosidl_parser.definition import UNSIGNED_INTEGER_TYPES from collections import OrderedDict import numpy imports = OrderedDict() +if message.structure.members: + imports.setdefault( + 'import rosidl_parser.definition', []) # used for SLOT_TYPES for member in message.structure.members: if ( isinstance(member.type, AbstractNestedType) and @@ -65,7 +69,7 @@ for member in message.structure.members: @(import_statement)@ @[ if import_statement not in import_statements]@ @{import_statements.add(import_statement)}@ - # noqa: E402@ + # noqa: E402, I100@ @[ end if] @[ end for]@ @[end if]@ @@ -227,6 +231,39 @@ string@ @[end for]@ } + SLOT_TYPES = ( +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, AbstractNestedType): + type_ = type_.value_type +}@ + @ +@[ if isinstance(member.type, AbstractNestedType)]@ +@(member.type.__class__.__module__).@(member.type.__class__.__name__)(@ +@[ end if]@ +@# the typename of the non-nested type or the nested value_type +@(type_.__class__.__module__).@(type_.__class__.__name__)(@ +@[ if isinstance(type_, BasicType)]@ +'@(type_.typename)'@ +@[ elif isinstance(type_, AbstractGenericString) and type_.has_maximum_size()]@ +@(type_.maximum_size)@ +@[ elif isinstance(type_, NamespacedType)]@ +[@(', '.join("'%s'" % n for n in type_.namespaces))], '@(type_.name)'@ +@[ end if]@ +)@ +@[ if isinstance(member.type, Array)]@ +, @(member.type.size)@ +@[ elif isinstance(member.type, BoundedSequence)]@ +, @(member.type.maximum_size)@ +@[ end if]@ +@[ if isinstance(member.type, AbstractNestedType)]@ +)@ +@[ end if]@ +, # noqa: E501 +@[end for]@ + ) + def __init__(self, **kwargs): assert all('_' + key in self.__slots__ for key in kwargs.keys()), \ 'Invalid arguments passed to constructor: %s' % \ diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index e4a7cf59..6c4f23e0 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -16,6 +16,7 @@ import numpy import pytest + from rosidl_generator_py.msg import Constants from rosidl_generator_py.msg import Nested from rosidl_generator_py.msg import Primitives @@ -24,6 +25,13 @@ from rosidl_generator_py.msg import Various from rosidl_generator_py.msg import WStrings +from rosidl_parser.definition import Array +from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import BoundedString +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import UnboundedSequence +from rosidl_parser.definition import UnboundedString + def test_strings(): a = Strings() @@ -314,7 +322,7 @@ def test_slot_attributes(): assert expected_slot_type == nested_slot_types_dict[expected_field] -def test_primative_slot_attributes(): +def test_string_slot_attributes(): b = StringArrays() assert hasattr(b, 'get_fields_and_field_types') assert hasattr(b, '__slots__') @@ -349,3 +357,63 @@ def test_modifying_slot_fields_and_types(): string_slot_types_dict_len = len(string_slot_types_dict) string_slot_types_dict[1] = 2 assert len(getattr(b, 'get_fields_and_field_types')()) == string_slot_types_dict_len + + +def test_slot_types(): + a = Nested() + assert hasattr(a, 'SLOT_TYPES') + assert hasattr(a, '__slots__') + nested_slot_types = Nested.SLOT_TYPES + nested_slots = getattr(a, '__slots__') + assert len(nested_slot_types) == len(nested_slots) + assert isinstance(nested_slot_types[0], NamespacedType) + assert nested_slot_types[0].namespaces == ['rosidl_generator_py', 'msg'] + assert nested_slot_types[0].name == 'Primitives' + + assert isinstance(nested_slot_types[1], Array) + assert isinstance(nested_slot_types[1].value_type, NamespacedType) + assert nested_slot_types[1].value_type.namespaces == \ + ['rosidl_generator_py', 'msg'] + assert nested_slot_types[1].value_type.name == 'Primitives' + + assert isinstance(nested_slot_types[2], BoundedSequence) + assert isinstance(nested_slot_types[2].value_type, NamespacedType) + assert nested_slot_types[2].value_type.namespaces == \ + ['rosidl_generator_py', 'msg'] + assert nested_slot_types[2].value_type.name == 'Primitives' + + assert isinstance(nested_slot_types[3], UnboundedSequence) + assert isinstance(nested_slot_types[3].value_type, NamespacedType) + assert nested_slot_types[3].value_type.namespaces == \ + ['rosidl_generator_py', 'msg'] + assert nested_slot_types[3].value_type.name == 'Primitives' + + +def test_string_slot_types(): + b = StringArrays() + assert hasattr(b, 'SLOT_TYPES') + assert hasattr(b, '__slots__') + string_slot_types = StringArrays.SLOT_TYPES + string_slots = getattr(b, '__slots__') + assert len(string_slot_types) == len(string_slots) + + assert isinstance(string_slot_types[0], Array) + assert isinstance(string_slot_types[0].value_type, BoundedString) + assert string_slot_types[0].size == 3 + assert string_slot_types[0].value_type.maximum_size == 5 + + assert isinstance(string_slot_types[1], BoundedSequence) + assert isinstance(string_slot_types[1].value_type, BoundedString) + assert string_slot_types[1].maximum_size == 10 + assert string_slot_types[1].value_type.maximum_size == 5 + + assert isinstance(string_slot_types[2], UnboundedSequence) + assert isinstance(string_slot_types[2].value_type, BoundedString) + assert string_slot_types[2].value_type.maximum_size == 5 + + assert isinstance(string_slot_types[3], UnboundedSequence) + assert isinstance(string_slot_types[3].value_type, UnboundedString) + + assert isinstance(string_slot_types[4], Array) + assert isinstance(string_slot_types[4].value_type, UnboundedString) + assert string_slot_types[4].size == 3 diff --git a/rosidl_runtime_py/package.xml b/rosidl_runtime_py/package.xml index 9139c9a7..2de8eb66 100644 --- a/rosidl_runtime_py/package.xml +++ b/rosidl_runtime_py/package.xml @@ -11,6 +11,7 @@ python3-numpy python3-yaml + rosidl_parser ament_copyright ament_flake8 diff --git a/rosidl_runtime_py/rosidl_runtime_py/__init__.py b/rosidl_runtime_py/rosidl_runtime_py/__init__.py index 5f40b2ed..7a71aa92 100644 --- a/rosidl_runtime_py/rosidl_runtime_py/__init__.py +++ b/rosidl_runtime_py/rosidl_runtime_py/__init__.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .convert import get_message_slot_types from .convert import message_to_csv from .convert import message_to_ordereddict from .convert import message_to_yaml +from .import_message import import_message_from_namespaced_type from .set_message import set_message_fields __all__ = [ + 'get_message_slot_types', + 'import_message_from_namespaced_type', 'message_to_csv', 'message_to_ordereddict', 'message_to_yaml', diff --git a/rosidl_runtime_py/rosidl_runtime_py/convert.py b/rosidl_runtime_py/rosidl_runtime_py/convert.py index fc783f13..1babe73c 100644 --- a/rosidl_runtime_py/rosidl_runtime_py/convert.py +++ b/rosidl_runtime_py/rosidl_runtime_py/convert.py @@ -154,3 +154,13 @@ def _convert_value(value, truncate_length=None): # Assuming value is a message since it is neither a collection nor a primitive type value = message_to_ordereddict(value, truncate_length=truncate_length) return value + + +def get_message_slot_types(msg: Any) -> OrderedDict: + """ + Return an OrderedDict of the slot types of a message. + + :param msg: The ROS message to get members types from. + :returns: An OrderedDict with message member names as keys and slot types as values. + """ + return OrderedDict(zip([s[1:] for s in msg.__slots__], msg.SLOT_TYPES)) diff --git a/rosidl_runtime_py/rosidl_runtime_py/import_message.py b/rosidl_runtime_py/rosidl_runtime_py/import_message.py new file mode 100644 index 00000000..ceb11ad8 --- /dev/null +++ b/rosidl_runtime_py/rosidl_runtime_py/import_message.py @@ -0,0 +1,24 @@ +# Copyright 2019 Mikael Arguedas. +# +# 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 importlib +from typing import Any + +from rosidl_parser.definition import NamespacedType + + +def import_message_from_namespaced_type(message_type: NamespacedType) -> Any: + module = importlib.import_module( + '.'.join(message_type.value_type.namespaces)) + return getattr(module, message_type.value_type.name) diff --git a/rosidl_runtime_py/rosidl_runtime_py/set_message.py b/rosidl_runtime_py/rosidl_runtime_py/set_message.py index cfb36563..d84a7a4d 100644 --- a/rosidl_runtime_py/rosidl_runtime_py/set_message.py +++ b/rosidl_runtime_py/rosidl_runtime_py/set_message.py @@ -15,6 +15,11 @@ from typing import Any from typing import Dict +from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import NamespacedType +from rosidl_runtime_py.convert import get_message_slot_types +from rosidl_runtime_py.import_message import import_message_from_namespaced_type + def set_message_fields(msg: Any, values: Dict[str, str]) -> None: """ @@ -33,4 +38,13 @@ def set_message_fields(msg: Any, values: Dict[str, str]) -> None: except TypeError: value = field_type() set_message_fields(value, field_value) + rosidl_type = get_message_slot_types(msg)[field_name] + # Check if field is an array of ROS messages + if isinstance(rosidl_type, AbstractNestedType): + if isinstance(rosidl_type.value_type, NamespacedType): + field_elem_type = import_message_from_namespaced_type(rosidl_type) + for n in range(len(value)): + submsg = field_elem_type() + set_message_fields(submsg, value[n]) + value[n] = submsg setattr(msg, field_name, value) diff --git a/rosidl_runtime_py/test/rosidl_runtime_py/test_set_message.py b/rosidl_runtime_py/test/rosidl_runtime_py/test_set_message.py index a74a6d92..4f178d65 100644 --- a/rosidl_runtime_py/test/rosidl_runtime_py/test_set_message.py +++ b/rosidl_runtime_py/test/rosidl_runtime_py/test_set_message.py @@ -91,3 +91,39 @@ def test_set_message_fields_invalid(): invalid_type['int32_value'] = 'this is not an integer' with pytest.raises(ValueError): set_message_fields(msg, invalid_type) + + +def test_set_nested_namespaced_fields(): + unbounded_sequence_msg = message_fixtures.get_msg_unbounded_sequences()[1] + test_values = { + 'basic_types_values': [ + {'float64_value': 42.42, 'int8_value': 42}, + {'float64_value': 11.11, 'int8_value': 11} + ] + } + set_message_fields(unbounded_sequence_msg, test_values) + assert unbounded_sequence_msg.basic_types_values[0].float64_value == 42.42 + assert unbounded_sequence_msg.basic_types_values[0].int8_value == 42 + assert unbounded_sequence_msg.basic_types_values[0].uint8_value == 0 + assert unbounded_sequence_msg.basic_types_values[1].float64_value == 11.11 + assert unbounded_sequence_msg.basic_types_values[1].int8_value == 11 + assert unbounded_sequence_msg.basic_types_values[1].uint8_value == 0 + + arrays_msg = message_fixtures.get_msg_arrays()[0] + test_values = { + 'basic_types_values': [ + {'float64_value': 42.42, 'int8_value': 42}, + {'float64_value': 11.11, 'int8_value': 11}, + {'float64_value': 22.22, 'int8_value': 22}, + ] + } + set_message_fields(arrays_msg, test_values) + assert arrays_msg.basic_types_values[0].float64_value == 42.42 + assert arrays_msg.basic_types_values[0].int8_value == 42 + assert arrays_msg.basic_types_values[0].uint8_value == 0 + assert arrays_msg.basic_types_values[1].float64_value == 11.11 + assert arrays_msg.basic_types_values[1].int8_value == 11 + assert arrays_msg.basic_types_values[1].uint8_value == 0 + assert arrays_msg.basic_types_values[2].float64_value == 22.22 + assert arrays_msg.basic_types_values[2].int8_value == 22 + assert arrays_msg.basic_types_values[2].uint8_value == 0