Skip to content

Commit

Permalink
Using Generics for messages (#1239)
Browse files Browse the repository at this point in the history
* First draft of generics

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix Generic

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add legacy

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix import order

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* fix import order

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add Docstrings

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add Docstrings

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add generics support to Node

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Update type_support.py

Signed-off-by: Michael Carlstrom <36806982+InvincibleRMC@users.noreply.github.com>
Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to expand_topic_name (#1238)

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Creates Enum wrapper for ClockType and ClockChange (#1235)

* Testing out Enum wrapper for ClockType

* convert to rcl_clock_type_t

* Update create_time_point

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types (#1231)

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to exceptions.py (#1241)

* Add types to exception

* Add type checking guard

* Fix NotInitializedException

* Add missing defualt

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* added python3-yaml (#1242)

Signed-off-by: SnIcK <ido.samuelson@gmail.com>
Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to duration.py (#1233)

* Add types to logging_service.py (#1227)

* add types to logging_service

* Add types to duration.py

* Add newlines for class definintions

* update type alias name

* Update to use Protocols

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add Static Typing to Validate files (#1230)

* Add types to validate files

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* remove type annotations from docstrings

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* removed other type annotated docstrings

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

---------

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to TypeHash and moved away from __slots__ usage (#1232)

* Add types to TypeHash and moved away from __slots__ usage

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* remove docstring types

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

---------

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Time.py Types (#1237)

* Start typing time.py

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Testing out Enum wrapper for ClockType

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* convert to rcl_clock_type_t

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Undo Change to time_point.cpp

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Update create_time_point

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Lint fixes

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add debug message

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Remove test file

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Try extending the type assert

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to logging_service.py (#1227)

* add types to logging_service

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to duration.py

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add newlines for class definintions

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* update type alias name

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Remove newline

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Merge?

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix failed merge

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Update to use Protocols

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix import error

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to time.py

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Linty

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

---------

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Revert "Add types to TypeHash and moved away from __slots__ usage (#1232)" (#1243)

This reverts commit b06baef.

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add back Type hash __slots__ and add test cases. (#1245)

* Add types to TypeHash and add test cases

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add types to context.py (#1240)

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* fix pub and sub

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Update LifecyclePublisher

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix docstring

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* serialization generic

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* serialization generic

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Update type_support.py

Signed-off-by: Michael Carlstrom <36806982+InvincibleRMC@users.noreply.github.com>

---------

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>
Signed-off-by: Michael Carlstrom <36806982+InvincibleRMC@users.noreply.github.com>
Signed-off-by: SnIcK <ido.samuelson@gmail.com>
Signed-off-by: Shane Loretz <sloretz@intrinsic.ai>
Co-authored-by: SnIcK <ido.samuelson@gmail.com>
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
Co-authored-by: Shane Loretz <sloretz@intrinsic.ai>
  • Loading branch information
4 people authored May 3, 2024
1 parent 070132a commit 99f1ce9
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 39 deletions.
4 changes: 2 additions & 2 deletions rclpy/rclpy/lifecycle/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from typing import Union

from rclpy.publisher import MsgType
from rclpy.publisher import Publisher
from rclpy.type_support import MsgT

from .managed_entity import SimpleManagedEntity

Expand All @@ -28,7 +28,7 @@ def __init__(self, *args, **kwargs):
Publisher.__init__(self, *args, **kwargs)

@SimpleManagedEntity.when_enabled
def publish(self, msg: Union[MsgType, bytes]) -> None:
def publish(self, msg: Union[MsgT, bytes]) -> None:
"""
Publish a message if the lifecycle publisher is enabled.
Expand Down
14 changes: 8 additions & 6 deletions rclpy/rclpy/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
from rclpy.type_description_service import TypeDescriptionService
from rclpy.type_support import check_is_valid_msg_type
from rclpy.type_support import check_is_valid_srv_type
from rclpy.type_support import MsgT
from rclpy.utilities import get_default_context
from rclpy.validate_full_topic_name import validate_full_topic_name
from rclpy.validate_namespace import validate_namespace
Expand All @@ -93,8 +94,9 @@

HIDDEN_NODE_PREFIX = '_'

# Used for documentation purposes only
# Left to support Legacy TypeVar.
MsgType = TypeVar('MsgType')

SrvType = TypeVar('SrvType')
SrvTypeRequest = TypeVar('SrvTypeRequest')
SrvTypeResponse = TypeVar('SrvTypeResponse')
Expand Down Expand Up @@ -1499,15 +1501,15 @@ def resolve_service_name(

def create_publisher(
self,
msg_type,
msg_type: Type[MsgT],
topic: str,
qos_profile: Union[QoSProfile, int],
*,
callback_group: Optional[CallbackGroup] = None,
event_callbacks: Optional[PublisherEventCallbacks] = None,
qos_overriding_options: Optional[QoSOverridingOptions] = None,
publisher_class: Type[Publisher] = Publisher,
) -> Publisher:
) -> Publisher[MsgT]:
"""
Create a new publisher.
Expand Down Expand Up @@ -1573,16 +1575,16 @@ def create_publisher(

def create_subscription(
self,
msg_type,
msg_type: Type[MsgT],
topic: str,
callback: Callable[[MsgType], None],
callback: Callable[[MsgT], None],
qos_profile: Union[QoSProfile, int],
*,
callback_group: Optional[CallbackGroup] = None,
event_callbacks: Optional[SubscriptionEventCallbacks] = None,
qos_overriding_options: Optional[QoSOverridingOptions] = None,
raw: bool = False
) -> Subscription:
) -> Subscription[MsgT]:
"""
Create a new subscription.
Expand Down
15 changes: 8 additions & 7 deletions rclpy/rclpy/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TypeVar, Union
from typing import Generic, List, Type, TypeVar, Union

from rclpy.callback_groups import CallbackGroup
from rclpy.duration import Duration
from rclpy.event_handler import EventHandler
from rclpy.event_handler import PublisherEventCallbacks
from rclpy.event_handler import EventHandler, PublisherEventCallbacks
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
from rclpy.qos import QoSProfile
from rclpy.type_support import MsgT

# Left to support Legacy TypeVars.
MsgType = TypeVar('MsgType')


class Publisher:
class Publisher(Generic[MsgT]):

def __init__(
self,
publisher_impl: _rclpy.Publisher,
msg_type: MsgType,
msg_type: Type[MsgT],
topic: str,
qos_profile: QoSProfile,
event_callbacks: PublisherEventCallbacks,
Expand All @@ -54,10 +55,10 @@ def __init__(
self.topic = topic
self.qos_profile = qos_profile

self.event_handlers: EventHandler = event_callbacks.create_event_handlers(
self.event_handlers: List[EventHandler] = event_callbacks.create_event_handlers(
callback_group, publisher_impl, topic)

def publish(self, msg: Union[MsgType, bytes]) -> None:
def publish(self, msg: Union[MsgT, bytes]) -> None:
"""
Send a message to the topic for the publisher.
Expand Down
7 changes: 4 additions & 3 deletions rclpy/rclpy/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
# 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.
from typing import Type

from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
from rclpy.type_support import check_for_type_support
from rclpy.type_support import check_for_type_support, Msg, MsgT


def serialize_message(message) -> bytes:
def serialize_message(message: Msg) -> bytes:
"""
Serialize a ROS message.
Expand All @@ -29,7 +30,7 @@ def serialize_message(message) -> bytes:
return _rclpy.rclpy_serialize(message, message_type)


def deserialize_message(serialized_message: bytes, message_type):
def deserialize_message(serialized_message: bytes, message_type: Type[MsgT]) -> MsgT:
"""
Deserialize a ROS message.
Expand Down
22 changes: 11 additions & 11 deletions rclpy/rclpy/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.


from enum import Enum
import inspect
from typing import Callable
from typing import TypeVar
from typing import Callable, Generic, List, Type, TypeVar

from rclpy.callback_groups import CallbackGroup
from rclpy.event_handler import EventHandler
from rclpy.event_handler import SubscriptionEventCallbacks
from rclpy.event_handler import EventHandler, SubscriptionEventCallbacks
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
from rclpy.qos import QoSProfile
from rclpy.type_support import MsgT


# For documentation only
# Left to support Legacy TypeVars.
MsgType = TypeVar('MsgType')


class Subscription:
class Subscription(Generic[MsgT]):

class CallbackType(Enum):
MessageOnly = 0
Expand All @@ -37,9 +37,9 @@ class CallbackType(Enum):
def __init__(
self,
subscription_impl: _rclpy.Subscription,
msg_type: MsgType,
msg_type: Type[MsgT],
topic: str,
callback: Callable,
callback: Callable[[MsgT], None],
callback_group: CallbackGroup,
qos_profile: QoSProfile,
raw: bool,
Expand Down Expand Up @@ -73,7 +73,7 @@ def __init__(
self.qos_profile = qos_profile
self.raw = raw

self.event_handlers: EventHandler = event_callbacks.create_event_handlers(
self.event_handlers: List[EventHandler] = event_callbacks.create_event_handlers(
callback_group, subscription_impl, topic)

def get_publisher_count(self) -> int:
Expand Down Expand Up @@ -102,11 +102,11 @@ def topic_name(self):
return self.__subscription.get_topic_name()

@property
def callback(self):
def callback(self) -> Callable[[MsgT], None]:
return self._callback

@callback.setter
def callback(self, value):
def callback(self, value: Callable[[MsgT], None]) -> None:
self._callback = value
self._callback_type = Subscription.CallbackType.MessageOnly
try:
Expand Down
72 changes: 62 additions & 10 deletions rclpy/rclpy/type_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,64 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional, Protocol, Type, TypeVar, Union

from rclpy.exceptions import NoTypeSupportImportedException


def check_for_type_support(msg_or_srv_type):
class PyCapsule(Protocol):
"""Alias for PyCapsule Pybind object."""

pass


# Done because metaclasses need to inherit from type
ProtocolType: Type = type(Protocol)


class CommonMsgSrvMetaClass(ProtocolType):
"""Shared attributes between messages and services."""

_TYPE_SUPPORT: Optional[PyCapsule]

@classmethod
def __import_type_support__(cls) -> None:
...


class MsgMetaClass(CommonMsgSrvMetaClass):
"""Generic Message Metaclass Alias."""

_CREATE_ROS_MESSAGE: Optional[PyCapsule]
_CONVERT_FROM_PY: Optional[PyCapsule]
_CONVERT_TO_PY: Optional[PyCapsule]
_DESTROY_ROS_MESSAGE: Optional[PyCapsule]


class Msg(Protocol, metaclass=MsgMetaClass):
"""Generic Message Type Alias."""

pass


# Could likely be improved if generic across Request, Response, Event
class Srv(Protocol, metaclass=CommonMsgSrvMetaClass):
"""Generic Service Type Alias."""

pass


MsgT = TypeVar('MsgT', bound=Msg)
SrvT = TypeVar('SrvT', bound=Srv)

SrvRequestT = TypeVar('SrvRequestT', bound=Msg)
SrvResponseT = TypeVar('SrvResponseT', bound=Msg)
SrvEventT = TypeVar('SrvEventT', bound=Msg)


def check_for_type_support(msg_or_srv_type: Type[Union[Msg, Srv]]) -> None:
try:
ts = msg_or_srv_type.__class__._TYPE_SUPPORT
ts = msg_or_srv_type._TYPE_SUPPORT
except AttributeError as e:
e.args = (
e.args[0] +
Expand All @@ -26,19 +78,19 @@ def check_for_type_support(msg_or_srv_type):
*e.args[1:])
raise
if ts is None:
msg_or_srv_type.__class__.__import_type_support__()
if msg_or_srv_type.__class__._TYPE_SUPPORT is None:
msg_or_srv_type.__import_type_support__()
if msg_or_srv_type._TYPE_SUPPORT is None:
raise NoTypeSupportImportedException()


def check_is_valid_msg_type(msg_type):
def check_is_valid_msg_type(msg_type: Type[Msg]) -> None:
check_for_type_support(msg_type)
try:
assert None not in (
msg_type.__class__._CREATE_ROS_MESSAGE,
msg_type.__class__._CONVERT_FROM_PY,
msg_type.__class__._CONVERT_TO_PY,
msg_type.__class__._DESTROY_ROS_MESSAGE,
msg_type._CREATE_ROS_MESSAGE,
msg_type._CONVERT_FROM_PY,
msg_type._CONVERT_TO_PY,
msg_type._DESTROY_ROS_MESSAGE,
)
except (AssertionError, AttributeError):
raise RuntimeError(
Expand All @@ -47,7 +99,7 @@ def check_is_valid_msg_type(msg_type):
) from None


def check_is_valid_srv_type(srv_type):
def check_is_valid_srv_type(srv_type: Type[Srv]) -> None:
check_for_type_support(srv_type)
try:
assert None not in (
Expand Down

0 comments on commit 99f1ce9

Please sign in to comment.