Skip to content

Use absolute imports for generated local type support#261

Open
Lidang-Jiang wants to merge 1 commit intoros2:rollingfrom
Lidang-Jiang:fix/absolute-local-imports
Open

Use absolute imports for generated local type support#261
Lidang-Jiang wants to merge 1 commit intoros2:rollingfrom
Lidang-Jiang:fix/absolute-local-imports

Conversation

@Lidang-Jiang
Copy link
Copy Markdown

@Lidang-Jiang Lidang-Jiang commented Apr 29, 2026

Summary

Fixes #257.

  • Generate absolute module imports for namespaced message field types, e.g. import builtin_interfaces.msg.
  • Refer to namespaced types by their fully qualified module path in type support imports, default construction, annotations, and check_fields assertions.
  • Add regression interfaces covering scalar, fixed-size array, and unbounded sequence fields using builtin_interfaces/Duration.

Did you use Generative AI?

Yes. I used OpenAI Codex (GPT-5) to help investigate the issue, prepare the code and test changes, and draft/update this PR description. I reviewed the changes and ran the local tests listed below before opening the PR.

Before / After

Before

Command:

git show upstream/rolling:rosidl_generator_py/resource/_msg.py.em | sed -n '178,207p'

Output:

        type_ = type_.value_type
    if isinstance(type_, NamespacedType):
        if (
            type_.name.endswith(SERVICE_RESPONSE_MESSAGE_SUFFIX) or
            type_.name.endswith(SERVICE_REQUEST_MESSAGE_SUFFIX)
        ):
            continue
        if (
            type_.name.endswith(ACTION_GOAL_SUFFIX) or
            type_.name.endswith(ACTION_RESULT_SUFFIX) or
            type_.name.endswith(ACTION_FEEDBACK_SUFFIX)
        ):
            action_name, suffix = type_.name.rsplit('_', 1)
            typename = (*type_.namespaces, action_name, action_name + '.' + suffix)
        else:
            typename = (*type_.namespaces, type_.name, type_.name)
        importable_typesupports.add(typename)
}@
@[for typename in sorted(importable_typesupports)]@

            from @('.'.join(typename[:-2])) import @(typename[-2])
            if @(typename[-1])._TYPE_SUPPORT is None:
                @(typename[-1]).__import_type_support__()
@[end for]@
After

Command:

rg -n "builtin_interfaces\\.msg|from builtin_interfaces\\.msg" \
  build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py \
  build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py

Output:

build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:27:    import builtin_interfaces.msg  # noqa: E402, I100, I201, I300
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:72:            import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:73:            if builtin_interfaces.msg.Duration._TYPE_SUPPORT is None:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:74:                builtin_interfaces.msg.Duration.__import_type_support__()
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:107:                 array_data: typing.Optional[collections.abc.Sequence[builtin_interfaces.msg.Duration]] = None,  # noqa: E501
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:108:                 sequence_data: typing.Optional[collections.abc.Sequence[builtin_interfaces.msg.Duration]] = None,  # noqa: E501
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:114:        import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:115:        self.array_data = array_data if array_data is not None else [builtin_interfaces.msg.Duration() for x in range(2)]
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:162:    def array_data(self) -> typing.Annotated[typing.Any, list[builtin_interfaces.msg.Duration]]:   # typing.Annotated can be remove after mypy 1.16+ see mypy#3004
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:167:    def array_data(self, value: collections.abc.Sequence[builtin_interfaces.msg.Duration]) -> None:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:174:        import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:186:                     all(isinstance(v, builtin_interfaces.msg.Duration) for v in value) and
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:188:                    "The 'array_data' field must be sequence with length 2 and each value of type 'builtin_interfaces.msg.Duration'"
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:196:    def sequence_data(self) -> typing.Annotated[typing.Any, list[builtin_interfaces.msg.Duration]]:   # typing.Annotated can be remove after mypy 1.16+ see mypy#3004
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:201:    def sequence_data(self, value: collections.abc.Sequence[builtin_interfaces.msg.Duration]) -> None:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:208:        import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:219:                     all(isinstance(v, builtin_interfaces.msg.Duration) for v in value) and
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration_array_sequence.py:221:                    "The 'sequence_data' field must be sequence and each value of type 'builtin_interfaces.msg.Duration'"
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:27:    import builtin_interfaces.msg  # noqa: E402, I100, I201, I300
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:72:            import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:73:            if builtin_interfaces.msg.Duration._TYPE_SUPPORT is None:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:74:                builtin_interfaces.msg.Duration.__import_type_support__()
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:104:                 data: typing.Optional[builtin_interfaces.msg.Duration] = None,  # noqa: E501
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:110:        import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:111:        self.data = data if data is not None else builtin_interfaces.msg.Duration()
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:155:    def data(self) -> builtin_interfaces.msg.Duration:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:160:    def data(self, value: builtin_interfaces.msg.Duration) -> None:
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:161:        import builtin_interfaces.msg
build_pr/rosidl_generator_py/rosidl_generator_py/rosidl_generator_py/msg/_duration.py:168:                    isinstance(value, builtin_interfaces.msg.Duration), \

Tests

Command:

env -i HOME=$HOME USER=$USER PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin bash -c 'source /opt/ros/rolling/setup.bash && export PYTHONPATH=$WORKSPACE/public-oss/ros2-prs/rosidl_python-257/rosidl_generator_py:${PYTHONPATH:-} && colcon --log-base log_pr build --paths $WORKSPACE/public-oss/ros2-prs/deps/ament_cmake_ros/ament_cmake_ros_core $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_pycommon $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_buffer $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_runtime_c $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_buffer_py rosidl_generator_py --packages-select rosidl_generator_py --allow-overriding rosidl_generator_py rosidl_pycommon rosidl_runtime_c --event-handlers console_direct+ --build-base build_pr --install-base install_pr && colcon --log-base log_pr test --paths $WORKSPACE/public-oss/ros2-prs/deps/ament_cmake_ros/ament_cmake_ros_core $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_pycommon $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_buffer $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_runtime_c $WORKSPACE/public-oss/ros2-prs/rosidl-911/rosidl_buffer_py rosidl_generator_py --packages-select rosidl_generator_py --allow-overriding rosidl_generator_py rosidl_pycommon rosidl_runtime_c --event-handlers console_direct+ --build-base build_pr --install-base install_pr && colcon test-result --verbose --test-result-base build_pr'

Result:

Summary: 1298 tests, 0 errors, 0 failures, 397 skipped

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
@christophebedard
Copy link
Copy Markdown
Member

@Lidang-Jiang did you use AI to write all/some of this code or open this PR?

@Lidang-Jiang
Copy link
Copy Markdown
Author

Thanks for checking. Yes, I used OpenAI Codex (GPT-5) to help investigate the issue, prepare the code and test changes, and draft/update this PR description. I updated the PR body with a Generative AI disclosure section as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Message named pkg/Duration with field builtin_interfaces/Duration is invalid

2 participants