Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

store types as tuple of abstract types #33

Merged
merged 4 commits into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion rosidl_generator_py/resource/_msg.py.em
Original file line number Diff line number Diff line change
Expand Up @@ -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
}@
@#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Expand All @@ -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
Expand Down Expand Up @@ -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]@
Expand Down Expand Up @@ -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' % \
Expand Down
70 changes: 69 additions & 1 deletion rosidl_generator_py/test/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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__')
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions rosidl_runtime_py/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<exec_depend>python3-numpy</exec_depend>
<exec_depend>python3-yaml</exec_depend>
<exec_depend>rosidl_parser</exec_depend>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
Expand Down
4 changes: 4 additions & 0 deletions rosidl_runtime_py/rosidl_runtime_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 10 additions & 0 deletions rosidl_runtime_py/rosidl_runtime_py/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
24 changes: 24 additions & 0 deletions rosidl_runtime_py/rosidl_runtime_py/import_message.py
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 14 additions & 0 deletions rosidl_runtime_py/rosidl_runtime_py/set_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand All @@ -33,4 +38,13 @@ def set_message_fields(msg: Any, values: Dict[str, str]) -> None:
except TypeError:
value = field_type()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised this works in cases where the field type is numpy.ndarray or array.array. Can you please also add a test with a static array (which would cover the numpy case).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it surprised me as well that the code comparing to list didn't need to be updated after the numpy array PR..

Static array test case added in 712d4da

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])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value[n] looks weird here? Can you please extend the existing test of set_message_fields to cover this case of a nested namespaced type to make sure this works as expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added test coverage in ad65970

value[n] = submsg
setattr(msg, field_name, value)
36 changes: 36 additions & 0 deletions rosidl_runtime_py/test/rosidl_runtime_py/test_set_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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