Skip to content

Python type annotation #157

Open
@MrBlenny

Description

@MrBlenny

Feature request

Feature description

It would be useful if the python classes included type annotations.

For example:

Parent.msg

Child[] children
Child child

Child.msg

uint8 some_child_content

Would generate something like:

+from perception_msgs.msg import Child

class Parent(metaclass=Metaclass_Parent):
    """Message class 'Parent'."""

    __slots__ = [
        '_children',
        '_child',
    ]

    _fields_and_field_types = {
        'children': 'sequence<perception_msgs/Child>',
        'child': 'perception_msgs/Child',
    }

    SLOT_TYPES = (
        rosidl_parser.definition.UnboundedSequence(rosidl_parser.definition.NamespacedType(['perception_msgs', 'msg'], 'Child')),  # noqa: E501
        rosidl_parser.definition.NamespacedType(['perception_msgs', 'msg'], 'Child'),  # noqa: E501
    )

-   def __init__(self, **kwargs):
+   def __init__(self, children: Optional[List[Child]]=None, child: Optional[Child]=None):
-       assert all('_' + key in self.__slots__ for key in kwargs.keys()), \
-           'Invalid arguments passed to constructor: %s' % \
-           ', '.join(sorted(k for k in kwargs.keys() if '_' + k not in self.__slots__))
-       self.children = kwargs.get('children', [])
-       from perception_msgs.msg import Child
-       self.child = kwargs.get('child', Child())
+       self.children = children or []
+       self.child = child or Child()

    def __repr__(self):
        typename = self.__class__.__module__.split('.')
        typename.pop()
        typename.append(self.__class__.__name__)
        args = []
        for s, t in zip(self.__slots__, self.SLOT_TYPES):
            field = getattr(self, s)
            fieldstr = repr(field)
            # We use Python array type for fields that can be directly stored
            # in them, and "normal" sequences for everything else.  If it is
            # a type that we store in an array, strip off the 'array' portion.
            if (
                isinstance(t, rosidl_parser.definition.AbstractSequence) and
                isinstance(t.value_type, rosidl_parser.definition.BasicType) and
                t.value_type.typename in ['float', 'double', 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64']
            ):
                if len(field) == 0:
                    fieldstr = '[]'
                else:
                    assert fieldstr.startswith('array(')
                    prefix = "array('X', "
                    suffix = ')'
                    fieldstr = fieldstr[len(prefix):-len(suffix)]
            args.append(s[1:] + '=' + fieldstr)
        return '%s(%s)' % ('.'.join(typename), ', '.join(args))

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        if self.children != other.children:
            return False
        if self.child != other.child:
            return False
        return True

    @classmethod
    def get_fields_and_field_types(cls):
        from copy import copy
        return copy(cls._fields_and_field_types)

    @property
-   def children(self):
+   def children(self) -> List[Child]:
        """Message field 'children'."""
        return self._children

    @children.setter
-   def children(self, value):
+   def children(self, value: List[Child]):
        if __debug__:
            from perception_msgs.msg import Child
            from collections.abc import Sequence
            from collections.abc import Set
            from collections import UserList
            from collections import UserString
            assert \
                ((isinstance(value, Sequence) or
                  isinstance(value, Set) or
                  isinstance(value, UserList)) and
                 not isinstance(value, str) and
                 not isinstance(value, UserString) and
                 all(isinstance(v, Child) for v in value) and
                 True), \
                "The 'children' field must be a set or sequence and each value of type 'Child'"
        self._children = value

    @property
-   def child(self):
+   def child(self) -> Child: 
        """Message field 'child'."""
        return self._child

    @child.setter
-   def child(self, value):
+   def child(self, value: Child):    
        if __debug__:
            from perception_msgs.msg import Child
            assert \
                isinstance(value, Child), \
                "The 'child' field must be a sub message of type 'Child'"
        self._child = value

Implementation considerations

  • There seems to be an issue with typing @property getters/setters. For me, using pylance the above syntax thinks child is of type property rather than type Child
  • Backwards compatibility requirements - do we need to support python < 3.8? I assume type annotations break old python.
  • Another path.... from dataclasses import dataclass gives us most of these features and more. Plus, it can be used with other libraries like dacite

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions