Skip to content

Commit 3c3bd8d

Browse files
Cast Sequence to list on assignment (with templates) (#249)
* test Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * move check fields generatoin into generate_py_impl Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * init Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * implementation complete Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * add tests for castig Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * remove comment Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * with bonus template Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * fix cmakelist Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * fix merge Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> * Make check_fields isolated Signed-off-by: Michael Carlstrom <rmc@carlstrom.com> --------- Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>
1 parent aad4cce commit 3c3bd8d

5 files changed

Lines changed: 214 additions & 156 deletions

File tree

rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ set(target_dependencies
109109
"${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_typesupport_entry_point.c.em"
110110
"${rosidl_generator_py_TEMPLATE_DIR}/_idl_support.c.em"
111111
"${rosidl_generator_py_TEMPLATE_DIR}/_idl.py.em"
112+
"${rosidl_generator_py_TEMPLATE_DIR}/_msg_check_fields.py.em"
112113
"${rosidl_generator_py_TEMPLATE_DIR}/_msg_pkg_typesupport_entry_point.c.em"
113114
"${rosidl_generator_py_TEMPLATE_DIR}/_msg_support.c.em"
114115
"${rosidl_generator_py_TEMPLATE_DIR}/_msg.py.em"

rosidl_generator_py/resource/_msg.py.em

Lines changed: 26 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
@# Included from rosidl_generator_py/resource/_idl.py.em
22
@{
3-
43
from rosidl_pycommon import convert_camel_case_to_lower_case_underscore
54
from rosidl_generator_py.generate_py_impl import constant_value_to_py
65
from rosidl_generator_py.generate_py_impl import get_python_type
@@ -484,177 +483,57 @@ if isinstance(member.type, (Array, AbstractSequence)):
484483

485484
@@@(member.name).setter@(noqa_string)
486485
def @(member.name)(self, value: @(type_annotations_setter[member.name])) -> None:@(noqa_string)
487-
488486
@[ if isinstance(member.type, AbstractNestedType)]@
489-
from collections.abc import Set
490-
if isinstance(value, Set):
487+
if isinstance(value, collections.abc.Set):
491488
import warnings
492489
warnings.warn(
493490
'Using set or subclass of set is deprecated,'
494491
' please use a subclass of collections.abc.Sequence like list',
495492
DeprecationWarning)
496-
@[ end if]@
497-
if self._check_fields:
498-
@[ if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@
499-
@[ if isinstance(member.type, Array)]@
500-
if isinstance(value, numpy.ndarray):
501-
assert value.dtype == @(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype']), \
502-
"The '@(member.name)' numpy.ndarray() must have the dtype of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'])'"
503-
assert value.size == @(member.type.size), \
504-
"The '@(member.name)' numpy.ndarray() must have a size of @(member.type.size)"
505-
self._@(member.name) = value
506-
return
507-
@[ elif isinstance(member.type, AbstractSequence)]@
508-
if isinstance(value, array.array):
509-
assert value.typecode == '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', \
510-
"The '@(member.name)' array.array() must have the type code of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])'"
511-
@[ if isinstance(member.type, BoundedSequence)]@
512-
assert len(value) <= @(member.type.maximum_size), \
513-
"The '@(member.name)' array.array() must have a size <= @(member.type.maximum_size)"
514-
@[ end if]@
515-
self._@(member.name) = value
516-
return
517-
@[ end if]@
518493
@[ end if]@
519494
@[ if isinstance(type_, NamespacedType)]@
520495
@[ if (
521496
type_.name.endswith(ACTION_GOAL_SUFFIX) or
522497
type_.name.endswith(ACTION_RESULT_SUFFIX) or
523498
type_.name.endswith(ACTION_FEEDBACK_SUFFIX)
524499
)]@
525-
from @('.'.join(type_.namespaces))._@(convert_camel_case_to_lower_case_underscore(type_.name.rsplit('_', 1)[0])) import @(type_.name)
500+
from @('.'.join(type_.namespaces))._@(convert_camel_case_to_lower_case_underscore(type_.name.rsplit('_', 1)[0])) import @(type_.name)
526501
@[ else]@
527-
from @('.'.join(type_.namespaces)) import @(type_.name)
502+
from @('.'.join(type_.namespaces)) import @(type_.name)
528503
@[ end if]@
529504
@[ end if]@
530-
@[ if isinstance(member.type, AbstractNestedType)]@
531-
from collections.abc import Sequence
532-
from collections import UserString
533-
@[ elif isinstance(type_, AbstractGenericString) and type_.has_maximum_size()]@
534-
from collections import UserString
535-
@[ elif isinstance(type_, BasicType) and type_.typename in CHARACTER_TYPES]@
536-
from collections import UserString
537-
@[ end if]@
538-
assert \
539-
@[ if isinstance(member.type, AbstractNestedType)]@
540-
((isinstance(value, Sequence) or
541-
isinstance(value, Set)) and
542-
not isinstance(value, str) and
543-
not isinstance(value, UserString) and
544-
@{assert_msg_suffixes = ['sequence']}@
545-
@[ if isinstance(type_, AbstractGenericString) and type_.has_maximum_size()]@
546-
all(len(val) <= @(type_.maximum_size) for val in value) and
547-
@{assert_msg_suffixes.append('and each string value not longer than %d' % type_.maximum_size)}@
548-
@[ end if]@
549-
@[ if isinstance(member.type, (Array, BoundedSequence))]@
550-
@[ if isinstance(member.type, BoundedSequence)]@
551-
len(value) <= @(member.type.maximum_size) and
552-
@{assert_msg_suffixes.insert(1, 'with length <= %d' % member.type.maximum_size)}@
553-
@[ else]@
554-
len(value) == @(member.type.size) and
555-
@{assert_msg_suffixes.insert(1, 'with length %d' % member.type.size)}@
556-
@[ end if]@
557-
@[ end if]@
558-
all(isinstance(v, @(get_python_type(type_))) for v in value) and
559-
@{assert_msg_suffixes.append("and each value of type '%s'" % get_python_type(type_))}@
560-
@[ if isinstance(type_, BasicType) and type_.typename in SIGNED_INTEGER_TYPES]@
561-
@{
562-
nbits = int(type_.typename[3:])
563-
bound = 2**(nbits - 1)
564-
}@
565-
all(val >= -@(bound) and val < @(bound) for val in value)), \
566-
@{assert_msg_suffixes.append('and each integer in [%d, %d]' % (-bound, bound - 1))}@
567-
@[ elif isinstance(type_, BasicType) and type_.typename in UNSIGNED_INTEGER_TYPES]@
568-
@{
569-
nbits = int(type_.typename[4:])
570-
bound = 2**nbits
571-
}@
572-
all(val >= 0 and val < @(bound) for val in value)), \
573-
@{assert_msg_suffixes.append('and each unsigned integer in [0, %d]' % (bound - 1))}@
574-
@[ elif isinstance(type_, BasicType) and type_.typename == 'char']@
575-
all(ord(val) >= 0 and ord(val) < 256 for val in value)), \
576-
@{assert_msg_suffixes.append('and each char in [0, 255]')}@
577-
@[ elif isinstance(type_, BasicType) and type_.typename in FLOATING_POINT_TYPES]@
578-
@[ if type_.typename == "float"]@
579-
@{
580-
name = "float"
581-
bound = 3.402823466e+38
582-
}@
583-
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
584-
@{assert_msg_suffixes.append('and each float in [%f, %f]' % (-bound, bound))}@
585-
@[ elif type_.typename == "double"]@
505+
586506
@{
587-
name = "double"
588-
bound = 1.7976931348623157e+308
507+
TEMPLATE(
508+
'_msg_check_fields.py.em',
509+
member=member,
510+
type_=type_,
511+
)
589512
}@
590-
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
591-
@{assert_msg_suffixes.append('and each double in [%f, %f]' % (-bound, bound))}@
513+
514+
@[ if isinstance(member.type, AbstractNestedType)]@
515+
@[ if isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@
516+
@[ if isinstance(member.type, Array)]@
517+
if isinstance(value, numpy.ndarray):
518+
@[ elif isinstance(member.type, AbstractSequence)]@
519+
if isinstance(value, array.array):
592520
@[ end if]@
593521
@[ else]@
594-
True), \
595-
@[ end if]@
596-
"The '@(member.name)' field must be @(' '.join(assert_msg_suffixes))"
597-
@[ elif isinstance(member.type, AbstractGenericString) and member.type.has_maximum_size()]@
598-
(isinstance(value, (str, UserString)) and
599-
len(value) <= @(member.type.maximum_size)), \
600-
"The '@(member.name)' field must be string value " \
601-
'not longer than @(type_.maximum_size)'
602-
@[ elif isinstance(type_, NamespacedType)]@
603-
isinstance(value, @(type_.name)), \
604-
"The '@(member.name)' field must be a sub message of type '@(type_.name)'"
605-
@[ elif isinstance(type_, BasicType) and type_.typename == 'octet']@
606-
(isinstance(value, (bytes, bytearray, memoryview)) and
607-
len(value) == 1), \
608-
"The '@(member.name)' field must be of type 'bytes' or 'ByteString' with length 1"
609-
@[ elif isinstance(type_, BasicType) and type_.typename == 'char']@
610-
(isinstance(value, (str, UserString)) and
611-
len(value) == 1 and ord(value) >= -128 and ord(value) < 128), \
612-
"The '@(member.name)' field must be of type 'str' or 'UserString' " \
613-
'with length 1 and the character ord() in [-128, 127]'
614-
@[ elif isinstance(type_, AbstractGenericString)]@
615-
isinstance(value, str), \
616-
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
617-
@[ elif isinstance(type_, BasicType) and type_.typename in (BOOLEAN_TYPE, *FLOATING_POINT_TYPES, *INTEGER_TYPES)]@
618-
isinstance(value, @(get_python_type(type_))), \
619-
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
620-
@[ if type_.typename in SIGNED_INTEGER_TYPES]@
621-
@{
622-
nbits = int(type_.typename[3:])
623-
bound = 2**(nbits - 1)
624-
}@
625-
assert value >= -@(bound) and value < @(bound), \
626-
"The '@(member.name)' field must be an integer in [@(-bound), @(bound - 1)]"
627-
@[ elif type_.typename in UNSIGNED_INTEGER_TYPES]@
628-
@{
629-
nbits = int(type_.typename[4:])
630-
bound = 2**nbits
631-
}@
632-
assert value >= 0 and value < @(bound), \
633-
"The '@(member.name)' field must be an unsigned integer in [0, @(bound - 1)]"
634-
@[ elif type_.typename in FLOATING_POINT_TYPES]@
635-
@[ if type_.typename == "float"]@
636-
@{
637-
name = "float"
638-
bound = 3.402823466e+38
639-
}@
640-
@[ elif type_.typename == "double"]@
641-
@{
642-
name = "double"
643-
bound = 1.7976931348623157e+308
644-
}@
645-
@[ end if]@
646-
assert not (value < -@(bound) or value > @(bound)) or math.isinf(value), \
647-
"The '@(member.name)' field must be a @(name) in [@(-bound), @(bound)]"
522+
if isinstance(value, list):
648523
@[ end if]@
649-
@[ else]@
650-
False
524+
self._@(member.name) = value
525+
return
651526
@[ end if]@
652-
@[ if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@
653-
@[ if isinstance(member.type, Array)]@
527+
@[ if isinstance(member.type, AbstractNestedType)]@
528+
@[ if isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@
529+
@[ if isinstance(member.type, Array)]@
654530
self._@(member.name) = numpy.array(value, dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype']))
655-
@[ elif isinstance(member.type, AbstractSequence)]@
531+
@[ elif isinstance(member.type, AbstractSequence)]@
656532
# type ignore below fixed in mypy 1.17+ see mypy#19421
657533
self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment]
534+
@[ end if]@
535+
@[ else]@
536+
self._@(member.name) = list(value)
658537
@[ end if]@
659538
@[ else]@
660539
self._@(member.name) = value
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
@{
2+
from rosidl_parser.definition import AbstractGenericString
3+
from rosidl_parser.definition import AbstractNestedType
4+
from rosidl_parser.definition import AbstractSequence
5+
from rosidl_parser.definition import Array
6+
from rosidl_parser.definition import BasicType
7+
from rosidl_parser.definition import BOOLEAN_TYPE
8+
from rosidl_parser.definition import INTEGER_TYPES
9+
from rosidl_parser.definition import BoundedSequence
10+
from rosidl_parser.definition import FLOATING_POINT_TYPES
11+
from rosidl_parser.definition import SIGNED_INTEGER_TYPES
12+
from rosidl_parser.definition import UNSIGNED_INTEGER_TYPES
13+
from rosidl_parser.definition import NamespacedType
14+
from rosidl_generator_py.generate_py_impl import get_python_type
15+
from rosidl_generator_py.generate_py_impl import SPECIAL_NESTED_BASIC_TYPES
16+
}@
17+
if self._check_fields:
18+
@[ if isinstance(member.type, AbstractNestedType)]@
19+
@[ if isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@
20+
@[ if isinstance(member.type, Array)]@
21+
if isinstance(value, numpy.ndarray):
22+
assert value.dtype == @(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype']), \
23+
"The '@(member.name)' numpy.ndarray() must have the dtype of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'])'"
24+
assert value.size == @(member.type.size), \
25+
"The '@(member.name)' numpy.ndarray() must have a size of @(member.type.size)"
26+
@[ elif isinstance(member.type, AbstractSequence)]@
27+
if isinstance(value, array.array):
28+
assert value.typecode == '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', \
29+
"The '@(member.name)' array.array() must have the type code of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])'"
30+
@[ if isinstance(member.type, BoundedSequence)]@
31+
assert len(value) <= @(member.type.maximum_size), \
32+
"The '@(member.name)' array.array() must have a size <= @(member.type.maximum_size)"
33+
@[ end if]@
34+
@[ end if]@
35+
@[ else]@
36+
if False: # Done for templating alignment
37+
pass
38+
@[ end if]@
39+
@[ else]@
40+
if False: # Done for templating alignment
41+
pass
42+
@[ end if]@
43+
else:
44+
assert \
45+
@[ if isinstance(member.type, AbstractNestedType)]@
46+
((isinstance(value, collections.abc.Sequence) or
47+
isinstance(value, collections.abc.Set)) and
48+
not isinstance(value, str) and
49+
not isinstance(value, collections.UserString) and
50+
@{assert_msg_suffixes = ['sequence']}@
51+
@[ if isinstance(type_, AbstractGenericString) and type_.has_maximum_size()]@
52+
all(len(val) <= @(type_.maximum_size) for val in value) and
53+
@{assert_msg_suffixes.append('and each string value not longer than %d' % type_.maximum_size)}@
54+
@[ end if]@
55+
@[ if isinstance(member.type, (Array, BoundedSequence))]@
56+
@[ if isinstance(member.type, BoundedSequence)]@
57+
len(value) <= @(member.type.maximum_size) and
58+
@{assert_msg_suffixes.insert(1, 'with length <= %d' % member.type.maximum_size)}@
59+
@[ else]@
60+
len(value) == @(member.type.size) and
61+
@{assert_msg_suffixes.insert(1, 'with length %d' % member.type.size)}@
62+
@[ end if]@
63+
@[ end if]@
64+
all(isinstance(v, @(get_python_type(type_))) for v in value) and
65+
@{assert_msg_suffixes.append("and each value of type '%s'" % get_python_type(type_))}@
66+
@[ if isinstance(type_, BasicType) and type_.typename in SIGNED_INTEGER_TYPES]@
67+
@{
68+
nbits = int(type_.typename[3:])
69+
bound = 2**(nbits - 1)
70+
}@
71+
all(val >= -@(bound) and val < @(bound) for val in value)), \
72+
@{assert_msg_suffixes.append('and each integer in [%d, %d]' % (-bound, bound - 1))}@
73+
@[ elif isinstance(type_, BasicType) and type_.typename in UNSIGNED_INTEGER_TYPES]@
74+
@{
75+
nbits = int(type_.typename[4:])
76+
bound = 2**nbits
77+
}@
78+
all(val >= 0 and val < @(bound) for val in value)), \
79+
@{assert_msg_suffixes.append('and each unsigned integer in [0, %d]' % (bound - 1))}@
80+
@[ elif isinstance(type_, BasicType) and type_.typename == 'char']@
81+
all(ord(val) >= 0 and ord(val) < 256 for val in value)), \
82+
@{assert_msg_suffixes.append('and each char in [0, 255]')}@
83+
@[ elif isinstance(type_, BasicType) and type_.typename in FLOATING_POINT_TYPES]@
84+
@[ if type_.typename == "float"]@
85+
@{
86+
name = "float"
87+
bound = 3.402823466e+38
88+
}@
89+
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
90+
@{assert_msg_suffixes.append('and each float in [%f, %f]' % (-bound, bound))}@
91+
@[ elif type_.typename == "double"]@
92+
@{
93+
name = "double"
94+
bound = 1.7976931348623157e+308
95+
}@
96+
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
97+
@{assert_msg_suffixes.append('and each double in [%f, %f]' % (-bound, bound))}@
98+
@[ end if]@
99+
@[ else]@
100+
True), \
101+
@[ end if]@
102+
"The '@(member.name)' field must be @(' '.join(assert_msg_suffixes))"
103+
@[ elif isinstance(member.type, AbstractGenericString) and member.type.has_maximum_size()]@
104+
(isinstance(value, (str, collections.UserString)) and
105+
len(value) <= @(member.type.maximum_size)), \
106+
"The '@(member.name)' field must be string value " \
107+
'not longer than @(type_.maximum_size)'
108+
@[ elif isinstance(type_, NamespacedType)]@
109+
isinstance(value, @(type_.name)), \
110+
"The '@(member.name)' field must be a sub message of type '@(type_.name)'"
111+
@[ elif isinstance(type_, BasicType) and type_.typename == 'octet']@
112+
(isinstance(value, (bytes, bytearray, memoryview)) and
113+
len(value) == 1), \
114+
"The '@(member.name)' field must be of type 'bytes' or 'ByteString' with length 1"
115+
@[ elif isinstance(type_, BasicType) and type_.typename == 'char']@
116+
(isinstance(value, (str, collections.UserString)) and
117+
len(value) == 1 and ord(value) >= -128 and ord(value) < 128), \
118+
"The '@(member.name)' field must be of type 'str' or 'collections.UserString' " \
119+
'with length 1 and the character ord() in [-128, 127]'
120+
@[ elif isinstance(type_, AbstractGenericString)]@
121+
isinstance(value, str), \
122+
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
123+
@[ elif isinstance(type_, BasicType) and type_.typename in (BOOLEAN_TYPE, *FLOATING_POINT_TYPES, *INTEGER_TYPES)]@
124+
isinstance(value, @(get_python_type(type_))), \
125+
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
126+
@[ if type_.typename in SIGNED_INTEGER_TYPES]@
127+
@{
128+
nbits = int(type_.typename[3:])
129+
bound = 2**(nbits - 1)
130+
}@
131+
assert value >= -@(bound) and value < @(bound), \
132+
"The '@(member.name)' field must be an integer in [@(-bound), @(bound - 1)]"
133+
@[ elif type_.typename in UNSIGNED_INTEGER_TYPES]@
134+
@{
135+
nbits = int(type_.typename[4:])
136+
bound = 2**nbits
137+
}@
138+
assert value >= 0 and value < @(bound), \
139+
"The '@(member.name)' field must be an unsigned integer in [0, @(bound - 1)]"
140+
@[ elif type_.typename in FLOATING_POINT_TYPES]@
141+
@[ if type_.typename == "float"]@
142+
@{
143+
name = "float"
144+
bound = 3.402823466e+38
145+
}@
146+
@[ elif type_.typename == "double"]@
147+
@{
148+
name = "double"
149+
bound = 1.7976931348623157e+308
150+
}@
151+
@[ end if]@
152+
assert not (value < -@(bound) or value > @(bound)) or math.isinf(value), \
153+
"The '@(member.name)' field must be a @(name) in [@(-bound), @(bound)]"
154+
@[ end if]@
155+
@[ else]@
156+
False
157+
@[ end if]@

0 commit comments

Comments
 (0)