From 164506e860fa1e5d0a8f45dd40c1b70c3cf87cac Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 17 Jan 2026 10:04:27 -0800 Subject: [PATCH 1/8] test mutable sequences Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 11 +++++++++-- .../rosidl_generator_py/generate_py_impl.py | 2 +- rosidl_generator_py/test/test_interfaces.py | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 98285ecb..4c55fdef 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -497,11 +497,19 @@ if isinstance(member.type, (Array, AbstractSequence)): @[ if isinstance(member.type, AbstractNestedType)]@ from collections.abc import Set + from collections.abc import Sequence + from collections.abc import MutableSequence if isinstance(value, Set): import warnings warnings.warn( 'Using set or subclass of set is deprecated,' - ' please use a subclass of collections.abc.Sequence like list', + ' please use a subclass of collections.abc.MutableSequence like list', + DeprecationWarning) + if isinstance(value, Sequence) and not isinstance(value, MutableSequence): + import warnings + warnings.warn( + 'Using a subclass of Sequence is deprecated,' + ' please use a subclass of collections.abc.MutableSequence like list', DeprecationWarning) @[ end if]@ if self._check_fields: @@ -538,7 +546,6 @@ if isinstance(member.type, (Array, AbstractSequence)): @[ end if]@ @[ end if]@ @[ if isinstance(member.type, AbstractNestedType)]@ - from collections.abc import Sequence from collections import UserString @[ elif isinstance(type_, AbstractGenericString) and type_.has_maximum_size()]@ from collections import UserString diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index c0cf7c9b..bbd91ece 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -414,7 +414,7 @@ def get_setter_and_getter_type(member: Member, type_imports: set[str]) -> tuple[ type_annotations_getter = f'typing.Annotated[typing.Any, {type_annotation}]' if isinstance(member.type, AbstractNestedType): - sequence_type = f'collections.abc.Sequence[{python_type}]' + sequence_type = f'collections.abc.MutableSequence[{python_type}]' if type_annotation != '': type_annotation = f'typing.Union[{type_annotation}, {sequence_type}]' diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index 8d11d423..74bf2108 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -532,6 +532,9 @@ def test_arrays() -> None: with pytest.warns(DeprecationWarning): Arrays(string_values={'bar', 'baz', 'foo'}) + with pytest.warns(DeprecationWarning): + Arrays(string_values=('bar', 'baz', 'foo')) + def test_bounded_sequences() -> None: msg = BoundedSequences(check_fields=True) From 3cce22d14820838a338b1b9d3904d01bd5a1332d Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 17 Jan 2026 10:30:43 -0800 Subject: [PATCH 2/8] Update comment Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 4c55fdef..0d2b0aa4 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -508,7 +508,7 @@ if isinstance(member.type, (Array, AbstractSequence)): if isinstance(value, Sequence) and not isinstance(value, MutableSequence): import warnings warnings.warn( - 'Using a subclass of Sequence is deprecated,' + 'Using a subclass of Sequence that isn't a subclass of MutableSequence is deprecated,' ' please use a subclass of collections.abc.MutableSequence like list', DeprecationWarning) @[ end if]@ From c3b6a87a632498ba1ec64f3528a10673a61c52d5 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 17 Jan 2026 11:31:04 -0800 Subject: [PATCH 3/8] add another type ignore Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 11 +++++-- .../rosidl_generator_py/generate_py_impl.py | 33 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 0d2b0aa4..f0582ac0 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -490,7 +490,12 @@ if isinstance(member.type, (Array, AbstractSequence)): @@builtins.property@(noqa_string) def @(member.name)(self) -> @(type_annotations_getter[member.name]):@(noqa_string)@(array_type_commment) """Message field '@(member.name)'.""" +@[ if isinstance(member.type, AbstractSequence)]@ + # type ignore below fixed in mypy 1.17+ + return self._@(member.name) # type: ignore[has-type] +@[ else]@ return self._@(member.name) +@[ end if]@ @@@(member.name).setter@(noqa_string) def @(member.name)(self, value: @(type_annotations_setter[member.name])) -> None:@(noqa_string) @@ -508,7 +513,7 @@ if isinstance(member.type, (Array, AbstractSequence)): if isinstance(value, Sequence) and not isinstance(value, MutableSequence): import warnings warnings.warn( - 'Using a subclass of Sequence that isn't a subclass of MutableSequence is deprecated,' + "Using a subclass of Sequence that isn't a subclass of MutableSequence is deprecated," ' please use a subclass of collections.abc.MutableSequence like list', DeprecationWarning) @[ end if]@ @@ -670,8 +675,8 @@ bound = 1.7976931348623157e+308 @[ if isinstance(member.type, Array)]@ self._@(member.name) = numpy.array(value, dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'])) @[ elif isinstance(member.type, AbstractSequence)]@ - # type ignore below fixed in mypy 1.17+ see mypy#19421 - self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment] + # type ignore below fixed in mypy 1.17+ + self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment, has-type] @[ end if]@ @[ else]@ self._@(member.name) = value diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index bbd91ece..bf590d9f 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -399,25 +399,26 @@ def get_setter_and_getter_type(member: Member, type_imports: set[str]) -> tuple[ type_annotation = '' type_annotations_getter = '' - if ( - isinstance(member.type, AbstractNestedType) and isinstance(type_, BasicType) and - type_.typename in SPECIAL_NESTED_BASIC_TYPES - ): - if isinstance(member.type, Array): - type_imports.add('import numpy.typing') - dtype = SPECIAL_NESTED_BASIC_TYPES[type_.typename]['dtype'] - type_annotation = f'numpy.typing.NDArray[{dtype}]' - elif isinstance(member.type, AbstractSequence): - type_annotation = f'array.array[{python_type}]' - - # Using Annotated because of mypy#3004 - type_annotations_getter = f'typing.Annotated[typing.Any, {type_annotation}]' - if isinstance(member.type, AbstractNestedType): sequence_type = f'collections.abc.MutableSequence[{python_type}]' - if type_annotation != '': - type_annotation = f'typing.Union[{type_annotation}, {sequence_type}]' + if (isinstance(type_, BasicType) and type_.typename in SPECIAL_NESTED_BASIC_TYPES): + + if isinstance(member.type, Array): + type_imports.add('import numpy.typing') + dtype = SPECIAL_NESTED_BASIC_TYPES[type_.typename]['dtype'] + real_type_annotation = f'numpy.typing.NDArray[{dtype}]' + used_type_annotation = 'typing.Any' + type_annotation = f'typing.Union[{real_type_annotation}, {sequence_type}]' + else: # Aka AbstractSequence + real_type_annotation = f'array.array[{python_type}]' + used_type_annotation = sequence_type + type_annotation = sequence_type + + # Using Annotated because of mypy#3004 + type_annotations_getter = \ + f'typing.Annotated[{used_type_annotation}, {real_type_annotation}]' + else: type_annotation = sequence_type From c0cb87e9c97174705b8a61b10520c8bbc34515f1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 17 Jan 2026 12:54:20 -0800 Subject: [PATCH 4/8] add caveat for numpy.ndarray Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 9 +++++++++ rosidl_generator_py/test/test_interfaces.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index f0582ac0..ba647a89 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -510,12 +510,21 @@ if isinstance(member.type, (Array, AbstractSequence)): 'Using set or subclass of set is deprecated,' ' please use a subclass of collections.abc.MutableSequence like list', DeprecationWarning) +@[ if isinstance(member.type, Array) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@ + if isinstance(value, Sequence) and not isinstance(value, (MutableSequence, numpy.ndarray)): + import warnings + warnings.warn( + "Using a subclass of Sequence that isn't a subclass of MutableSequence is deprecated," + ' please use a subclass of collections.abc.MutableSequence like list or a numpy.ndarray', + DeprecationWarning) +@[ else]@ if isinstance(value, Sequence) and not isinstance(value, MutableSequence): import warnings warnings.warn( "Using a subclass of Sequence that isn't a subclass of MutableSequence is deprecated," ' please use a subclass of collections.abc.MutableSequence like list', DeprecationWarning) +@[ end if]@ @[ end if]@ if self._check_fields: @[ if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@ diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index 74bf2108..86692e18 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -535,6 +535,9 @@ def test_arrays() -> None: with pytest.warns(DeprecationWarning): Arrays(string_values=('bar', 'baz', 'foo')) + msg4 = Arrays(int8_values=numpy.array([4, 5, 3], dtype=numpy.int8)) + assert msg3 == msg4 + def test_bounded_sequences() -> None: msg = BoundedSequences(check_fields=True) From 882b7d7a23412373d646c1aa5fd0823ab6e1fb8d Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 17 Jan 2026 22:17:17 -0800 Subject: [PATCH 5/8] add typing.cast for rhel workaround Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 1 + 1 file changed, 1 insertion(+) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index ba647a89..cc30755a 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -538,6 +538,7 @@ if isinstance(member.type, (Array, AbstractSequence)): return @[ elif isinstance(member.type, AbstractSequence)]@ if isinstance(value, array.array): + value = typing.cast(array.array, value) # Can be removed in mypy 1.0+ assert value.typecode == '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', \ "The '@(member.name)' array.array() must have the type code of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])'" @[ if isinstance(member.type, BoundedSequence)]@ From 2271a1390faa95997c62f712138bb5336b880b9a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 18 Jan 2026 09:21:08 -0800 Subject: [PATCH 6/8] revert typing.Any Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 1 - .../rosidl_generator_py/generate_py_impl.py | 37 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index cc30755a..ba647a89 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -538,7 +538,6 @@ if isinstance(member.type, (Array, AbstractSequence)): return @[ elif isinstance(member.type, AbstractSequence)]@ if isinstance(value, array.array): - value = typing.cast(array.array, value) # Can be removed in mypy 1.0+ assert value.typecode == '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', \ "The '@(member.name)' array.array() must have the type code of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])'" @[ if isinstance(member.type, BoundedSequence)]@ diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index bf590d9f..9e72a004 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -399,26 +399,25 @@ def get_setter_and_getter_type(member: Member, type_imports: set[str]) -> tuple[ type_annotation = '' type_annotations_getter = '' - if isinstance(member.type, AbstractNestedType): - sequence_type = f'collections.abc.MutableSequence[{python_type}]' - - if (isinstance(type_, BasicType) and type_.typename in SPECIAL_NESTED_BASIC_TYPES): - - if isinstance(member.type, Array): - type_imports.add('import numpy.typing') - dtype = SPECIAL_NESTED_BASIC_TYPES[type_.typename]['dtype'] - real_type_annotation = f'numpy.typing.NDArray[{dtype}]' - used_type_annotation = 'typing.Any' - type_annotation = f'typing.Union[{real_type_annotation}, {sequence_type}]' - else: # Aka AbstractSequence - real_type_annotation = f'array.array[{python_type}]' - used_type_annotation = sequence_type - type_annotation = sequence_type - - # Using Annotated because of mypy#3004 - type_annotations_getter = \ - f'typing.Annotated[{used_type_annotation}, {real_type_annotation}]' + if ( + isinstance(member.type, AbstractNestedType) and isinstance(type_, BasicType) and + type_.typename in SPECIAL_NESTED_BASIC_TYPES + ): + if isinstance(member.type, Array): + type_imports.add('import numpy.typing') + dtype = SPECIAL_NESTED_BASIC_TYPES[type_.typename]['dtype'] + type_annotation = f'numpy.typing.NDArray[{dtype}]' + elif isinstance(member.type, AbstractSequence): + type_annotation = f'array.array[{python_type}]' + + # Using Annotated because of mypy#3004 + type_annotations_getter = f'typing.Annotated[typing.Any, {type_annotation}]' + + if isinstance(member.type, AbstractNestedType): + sequence_type = f'collections.abc.Sequence[{python_type}]' + if type_annotation != '': + type_annotation = f'typing.Union[{type_annotation}, {sequence_type}]' else: type_annotation = sequence_type From b257a9a1cf175b3e9e9d0207d35763698fc24cdd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 18 Jan 2026 09:26:12 -0800 Subject: [PATCH 7/8] remove has-type exception Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 7 +------ .../rosidl_generator_py/generate_py_impl.py | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index ba647a89..c585ca8d 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -490,12 +490,7 @@ if isinstance(member.type, (Array, AbstractSequence)): @@builtins.property@(noqa_string) def @(member.name)(self) -> @(type_annotations_getter[member.name]):@(noqa_string)@(array_type_commment) """Message field '@(member.name)'.""" -@[ if isinstance(member.type, AbstractSequence)]@ - # type ignore below fixed in mypy 1.17+ - return self._@(member.name) # type: ignore[has-type] -@[ else]@ return self._@(member.name) -@[ end if]@ @@@(member.name).setter@(noqa_string) def @(member.name)(self, value: @(type_annotations_setter[member.name])) -> None:@(noqa_string) @@ -685,7 +680,7 @@ bound = 1.7976931348623157e+308 self._@(member.name) = numpy.array(value, dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'])) @[ elif isinstance(member.type, AbstractSequence)]@ # type ignore below fixed in mypy 1.17+ - self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment, has-type] + self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment] @[ end if]@ @[ else]@ self._@(member.name) = value diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index 9e72a004..c0cf7c9b 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -413,8 +413,8 @@ def get_setter_and_getter_type(member: Member, type_imports: set[str]) -> tuple[ # Using Annotated because of mypy#3004 type_annotations_getter = f'typing.Annotated[typing.Any, {type_annotation}]' - if isinstance(member.type, AbstractNestedType): - sequence_type = f'collections.abc.Sequence[{python_type}]' + if isinstance(member.type, AbstractNestedType): + sequence_type = f'collections.abc.Sequence[{python_type}]' if type_annotation != '': type_annotation = f'typing.Union[{type_annotation}, {sequence_type}]' From ea506a662e0a6ff673ff68ae842f18186e62b867 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 18 Jan 2026 09:37:58 -0800 Subject: [PATCH 8/8] add back mypy link Signed-off-by: Michael Carlstrom --- rosidl_generator_py/resource/_msg.py.em | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index c585ca8d..57819826 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -679,7 +679,7 @@ bound = 1.7976931348623157e+308 @[ if isinstance(member.type, Array)]@ self._@(member.name) = numpy.array(value, dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'])) @[ elif isinstance(member.type, AbstractSequence)]@ - # type ignore below fixed in mypy 1.17+ + # type ignore below fixed in mypy 1.17+ see mypy#19421 self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) # type: ignore[assignment] @[ end if]@ @[ else]@