Skip to content

Commit 4e8b071

Browse files
event_handler.py types (#1340)
* Add types to wait_for_message.py Signed-off-by: Michael Carlstrom <[email protected]> * Add copyright Signed-off-by: Michael Carlstrom <[email protected]> * re-run CI Signed-off-by: Michael Carlstrom <[email protected]> * re-run CI Signed-off-by: Michael Carlstrom <[email protected]> * move Handles into _rclpy_pybind11 Signed-off-by: Michael Carlstrom <[email protected]> * Move Handles into type stubs: Signed-off-by: Michael Carlstrom <[email protected]> * Move Handles into type stubs Signed-off-by: Michael Carlstrom <[email protected]> * move [] into string Signed-off-by: Michael Carlstrom <[email protected]> * fix imports Signed-off-by: Michael Carlstrom <[email protected]> * remove extra line Signed-off-by: Michael Carlstrom <[email protected]> * puy _rclpy.Publisher in quotes Signed-off-by: Michael Carlstrom <[email protected]> * fix capitalization Signed-off-by: Michael Carlstrom <[email protected]> * Add EventHandle Constructor Signed-off-by: Michael Carlstrom <[email protected]> * Use RuntimeError for context Signed-off-by: Michael Carlstrom <[email protected]> * Add TYPE_CHECKING import Signed-off-by: Michael Carlstrom <[email protected]> * init Signed-off-by: Michael Carlstrom <[email protected]> * remove .vscode file Signed-off-by: Michael Carlstrom <[email protected]> * move into string Signed-off-by: Michael Carlstrom <[email protected]> * fix flake8 Signed-off-by: Michael Carlstrom <[email protected]> --------- Signed-off-by: Michael Carlstrom <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent ba72a01 commit 4e8b071

File tree

5 files changed

+179
-63
lines changed

5 files changed

+179
-63
lines changed

rclpy/rclpy/event_handler.py

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from enum import IntEnum
15+
from __future__ import annotations
16+
17+
from types import TracebackType
1618
from typing import Any
1719
from typing import Callable
1820
from typing import List
1921
from typing import Optional
22+
from typing import Type
23+
from typing import Union
2024
import warnings
2125

2226
import rclpy
@@ -30,50 +34,62 @@
3034
from typing_extensions import TypeAlias
3135

3236

33-
QoSPublisherEventType = _rclpy.rcl_publisher_event_type_t
34-
QoSSubscriptionEventType = _rclpy.rcl_subscription_event_type_t
37+
QoSPublisherEventType: TypeAlias = _rclpy.rcl_publisher_event_type_t
38+
QoSSubscriptionEventType: TypeAlias = _rclpy.rcl_subscription_event_type_t
3539

3640

3741
# Payload type for Subscription Deadline callback.
38-
QoSRequestedDeadlineMissedInfo = _rclpy.rmw_requested_deadline_missed_status_t
42+
QoSRequestedDeadlineMissedInfo: TypeAlias = _rclpy.rmw_requested_deadline_missed_status_t
3943

4044
# Payload type for Subscription Liveliness callback.
41-
QoSLivelinessChangedInfo = _rclpy.rmw_liveliness_changed_status_t
45+
QoSLivelinessChangedInfo: TypeAlias = _rclpy.rmw_liveliness_changed_status_t
4246

4347
# Payload type for Subscription Message Lost callback.
44-
QoSMessageLostInfo = _rclpy.rmw_message_lost_status_t
48+
QoSMessageLostInfo: TypeAlias = _rclpy.rmw_message_lost_status_t
4549

4650
# Payload type for Subscription Incompatible QoS callback.
47-
QoSRequestedIncompatibleQoSInfo = _rclpy.rmw_requested_qos_incompatible_event_status_t
51+
QoSRequestedIncompatibleQoSInfo: TypeAlias = _rclpy.rmw_requested_qos_incompatible_event_status_t
4852

4953
# Payload type for Subscription matched callback.
50-
QoSSubscriptionMatchedInfo = _rclpy.rmw_matched_status_t
54+
QoSSubscriptionMatchedInfo: TypeAlias = _rclpy.rmw_matched_status_t
5155

5256
# Payload type for Publisher Deadline callback.
53-
QoSOfferedDeadlineMissedInfo = _rclpy.rmw_offered_deadline_missed_status_t
57+
QoSOfferedDeadlineMissedInfo: TypeAlias = _rclpy.rmw_offered_deadline_missed_status_t
5458

5559
# Payload type for Publisher Liveliness callback.
56-
QoSLivelinessLostInfo = _rclpy.rmw_liveliness_lost_status_t
60+
QoSLivelinessLostInfo: TypeAlias = _rclpy.rmw_liveliness_lost_status_t
5761

5862
# Payload type for Publisher matched callback.
59-
QoSPublisherMatchedInfo = _rclpy.rmw_matched_status_t
63+
QoSPublisherMatchedInfo: TypeAlias = _rclpy.rmw_matched_status_t
6064

6165
"""
6266
Payload type for Publisher Incompatible QoS callback.
6367
6468
Mirrors rmw_offered_incompatible_qos_status_t from rmw/types.h
6569
"""
66-
QoSOfferedIncompatibleQoSInfo = QoSRequestedIncompatibleQoSInfo
70+
QoSOfferedIncompatibleQoSInfo: TypeAlias = QoSRequestedIncompatibleQoSInfo
6771

6872
# Payload type for Incompatible Type callback.
69-
IncompatibleTypeInfo = _rclpy.rmw_incompatible_type_status_t
73+
IncompatibleTypeInfo: TypeAlias = _rclpy.rmw_incompatible_type_status_t
7074

7175

7276
"""Raised when registering a callback for an event type that is not supported."""
73-
UnsupportedEventTypeError = _rclpy.UnsupportedEventTypeError
77+
UnsupportedEventTypeError: TypeAlias = _rclpy.UnsupportedEventTypeError
7478

7579

76-
EventHandlerData: TypeAlias = Optional[Any]
80+
EventHandlerData: TypeAlias = Optional[Union[
81+
QoSRequestedDeadlineMissedInfo,
82+
QoSLivelinessChangedInfo,
83+
QoSMessageLostInfo,
84+
QoSRequestedIncompatibleQoSInfo,
85+
IncompatibleTypeInfo,
86+
QoSSubscriptionMatchedInfo,
87+
QoSOfferedDeadlineMissedInfo,
88+
QoSLivelinessLostInfo,
89+
'_rclpy.rmw_offered_qos_incompatible_event_status_t',
90+
IncompatibleTypeInfo,
91+
QoSPublisherMatchedInfo
92+
]]
7793

7894

7995
class EventHandler(Waitable[EventHandlerData]):
@@ -83,23 +99,23 @@ def __init__(
8399
self,
84100
*,
85101
callback_group: CallbackGroup,
86-
callback: Callable,
87-
event_type: IntEnum,
88-
parent_impl,
89-
):
102+
callback: Callable[..., None],
103+
event_type: Union[QoSSubscriptionEventType, QoSPublisherEventType],
104+
parent_impl: 'Union[ _rclpy.Subscription[Any], _rclpy.Publisher[Any]]',
105+
) -> None:
90106
# Waitable init adds self to callback_group
91107
super().__init__(callback_group)
92108
self.event_type = event_type
93109
self.callback = callback
94110

95111
with parent_impl:
96-
self.__event = _rclpy.EventHandle(parent_impl, event_type)
112+
self.__event: '_rclpy.EventHandle[Any]' = _rclpy.EventHandle(parent_impl, event_type)
97113

98114
self._ready_to_take_data = False
99-
self._event_index = None
115+
self._event_index: Optional[int] = None
100116

101117
# Start Waitable API
102-
def is_ready(self, wait_set):
118+
def is_ready(self, wait_set: _rclpy.WaitSet) -> bool:
103119
"""Return True if entities are ready in the wait set."""
104120
if self._event_index is None:
105121
return False
@@ -121,20 +137,25 @@ async def execute(self, taken_data: EventHandlerData) -> None:
121137
return
122138
await rclpy.executors.await_or_execute(self.callback, taken_data)
123139

124-
def get_num_entities(self):
140+
def get_num_entities(self) -> NumberOfEntities:
125141
"""Return number of each type of entity used."""
126142
return NumberOfEntities(num_events=1)
127143

128-
def add_to_wait_set(self, wait_set):
144+
def add_to_wait_set(self, wait_set: _rclpy.WaitSet) -> None:
129145
"""Add entites to wait set."""
130146
with self.__event:
131147
self._event_index = wait_set.add_event(self.__event)
132148

133-
def __enter__(self):
149+
def __enter__(self) -> None:
134150
"""Mark event as in-use to prevent destruction while waiting on it."""
135151
self.__event.__enter__()
136152

137-
def __exit__(self, t, v, tb):
153+
def __exit__(
154+
self,
155+
t: Optional[Type[BaseException]],
156+
v: Optional[BaseException],
157+
tb: Optional[TracebackType],
158+
) -> None:
138159
"""Mark event as not-in-use to allow destruction after waiting on it."""
139160
self.__event.__exit__(t, v, tb)
140161

@@ -146,12 +167,7 @@ def destroy(self) -> None:
146167
category=DeprecationWarning, stacklevel=2)
147168
class QoSEventHandler(EventHandler):
148169

149-
def __init_subclass__(cls, **kwargs):
150-
warnings.warn('QoSEventHandler foo is deprecated, use EventHandler instead.',
151-
DeprecationWarning, stacklevel=2)
152-
super().__init_subclass__(**kwargs)
153-
154-
def __init__(self, *args, **kwargs):
170+
def __init__(self, *args: Any, **kwargs: Any) -> None:
155171
warnings.warn('QoSEventHandler is deprecated, use EventHandler instead.',
156172
DeprecationWarning, stacklevel=2)
157173
super().__init__(*args, **kwargs)
@@ -197,7 +213,7 @@ def __init__(
197213
self.use_default_callbacks = use_default_callbacks
198214

199215
def create_event_handlers(
200-
self, callback_group: CallbackGroup, subscription: '_rclpy.Subscription',
216+
self, callback_group: CallbackGroup, subscription: '_rclpy.Subscription[Any]',
201217
topic_name: str) -> List[EventHandler]:
202218
with subscription:
203219
logger = get_logger(subscription.get_logger_name())
@@ -215,7 +231,7 @@ def create_event_handlers(
215231
incompatible_qos_callback = self.incompatible_qos
216232
elif self.use_default_callbacks:
217233
# Register default callback when not specified
218-
def _default_incompatible_qos_callback(event):
234+
def _default_incompatible_qos_callback(event: QoSRequestedIncompatibleQoSInfo) -> None:
219235
policy_name = qos_policy_name_from_kind(event.last_policy_kind)
220236
logger.warn(
221237
"New publisher discovered on topic '{}', offering incompatible QoS. "
@@ -252,7 +268,7 @@ def _default_incompatible_qos_callback(event):
252268
incompatible_type_callback = self.incompatible_type
253269
elif self.use_default_callbacks:
254270
# Register default callback when not specified
255-
def _default_incompatible_type_callback(event):
271+
def _default_incompatible_type_callback(event: Any) -> None:
256272
logger.warn(
257273
"Incompatible type on topic '{}', no messages will be sent to it."
258274
.format(topic_name))
@@ -269,7 +285,7 @@ def _default_incompatible_type_callback(event):
269285
pass
270286

271287
if self.matched:
272-
event_handlers.append(QoSEventHandler(
288+
event_handlers.append(EventHandler(
273289
callback_group=callback_group,
274290
callback=self.matched,
275291
event_type=QoSSubscriptionEventType.RCL_SUBSCRIPTION_MATCHED,
@@ -315,7 +331,7 @@ def __init__(
315331
self.use_default_callbacks = use_default_callbacks
316332

317333
def create_event_handlers(
318-
self, callback_group: CallbackGroup, publisher: _rclpy.Publisher, topic_name: str,
334+
self, callback_group: CallbackGroup, publisher: '_rclpy.Publisher[Any]', topic_name: str,
319335
) -> List[EventHandler]:
320336
with publisher:
321337
logger = get_logger(publisher.get_logger_name())
@@ -340,7 +356,7 @@ def create_event_handlers(
340356
incompatible_qos_callback = self.incompatible_qos
341357
elif self.use_default_callbacks:
342358
# Register default callback when not specified
343-
def _default_incompatible_qos_callback(event):
359+
def _default_incompatible_qos_callback(event: QoSRequestedIncompatibleQoSInfo) -> None:
344360
policy_name = qos_policy_name_from_kind(event.last_policy_kind)
345361
logger.warn(
346362
"New subscription discovered on topic '{}', requesting incompatible QoS. "
@@ -363,7 +379,7 @@ def _default_incompatible_qos_callback(event):
363379
incompatible_type_callback = self.incompatible_type
364380
elif self.use_default_callbacks:
365381
# Register default callback when not specified
366-
def _default_incompatible_type_callback(event):
382+
def _default_incompatible_type_callback(event: Any) -> None:
367383
logger.warn(
368384
"Incompatible type on topic '{}', no messages will be sent to it."
369385
.format(topic_name))
@@ -380,7 +396,7 @@ def _default_incompatible_type_callback(event):
380396
pass
381397

382398
if self.matched:
383-
event_handlers.append(QoSEventHandler(
399+
event_handlers.append(EventHandler(
384400
callback_group=callback_group,
385401
callback=self.matched,
386402
event_type=QoSPublisherEventType.RCL_PUBLISHER_MATCHED,

rclpy/rclpy/impl/_rclpy_pybind11.pyi

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ from __future__ import annotations
1616

1717
from enum import Enum
1818
from types import TracebackType
19-
from typing import Any, Generic, Literal, overload, Sequence, TypeAlias, TypedDict
19+
from typing import Any, Generic, Literal, overload, Sequence, TypeAlias, TypedDict, TypeVar
2020

2121
from rclpy.clock import JumpHandle
2222
from rclpy.clock_type import ClockType
@@ -27,6 +27,9 @@ from rclpy.subscription import MessageInfo
2727
from rclpy.type_support import MsgT, Srv, SrvEventT, SrvRequestT, SrvResponseT
2828

2929

30+
T = TypeVar('T')
31+
32+
3033
def rclpy_remove_ros_args(pycli_args: Sequence[str]) -> list[str]:
3134
"""Remove ROS-specific arguments from argument vector."""
3235

@@ -202,20 +205,119 @@ class rcl_publisher_event_type_t(Enum):
202205
RCL_PUBLISHER_MATCHED = ...
203206

204207

205-
class EventHandle(Destroyable):
208+
class rmw_requested_deadline_missed_status_t:
209+
total_count: int
210+
total_count_change: int
211+
212+
213+
class rmw_liveliness_changed_status_t:
214+
alive_count: int
215+
not_alive_count: int
216+
alive_count_change: int
217+
not_alive_count_change: int
218+
219+
220+
class rmw_message_lost_status_t:
221+
total_count: int
222+
total_count_change: int
223+
224+
225+
class rmw_qos_policy_kind_e(Enum):
226+
_value_: int
227+
RMW_QOS_POLICY_INVALID = ...
228+
RMW_QOS_POLICY_DURABILITY = ...
229+
RMW_QOS_POLICY_DEADLINE = ...
230+
RMW_QOS_POLICY_LIVELINESS = ...
231+
RMW_QOS_POLICY_RELIABILITY = ...
232+
RMW_QOS_POLICY_HISTORY = ...
233+
RMW_QOS_POLICY_LIFESPAN = ...
234+
RMW_QOS_POLICY_DEPTH = ...
235+
RMW_QOS_POLICY_LIVELINESS_LEASE_DURATION = ...
236+
RMW_QOS_POLICY_AVOID_ROS_NAMESPACE_CONVENTIONS = ...
237+
238+
239+
rmw_qos_policy_kind_t = rmw_qos_policy_kind_e
240+
241+
242+
class rmw_requested_qos_incompatible_event_status_t:
243+
total_count: int
244+
total_count_change: int
245+
last_policy_kind: rmw_qos_policy_kind_t
246+
247+
248+
class rmw_matched_status_s:
249+
total_count: int
250+
total_count_change: int
251+
current_count: int
252+
current_count_change: int
253+
254+
255+
rmw_matched_status_t = rmw_matched_status_s
256+
257+
258+
class rmw_offered_deadline_missed_status_s:
259+
total_count: int
260+
total_count_change: int
261+
262+
263+
rmw_offered_deadline_missed_status_t = rmw_offered_deadline_missed_status_s
264+
265+
266+
class rmw_liveliness_lost_status_s:
267+
total_count: int
268+
total_count_change: int
269+
270+
271+
rmw_liveliness_lost_status_t = rmw_liveliness_lost_status_s
272+
273+
274+
class rmw_incompatible_type_status_s:
275+
total_count: int
276+
total_count_change: int
277+
278+
279+
rmw_incompatible_type_status_t = rmw_incompatible_type_status_s
280+
281+
282+
class rmw_qos_incompatible_event_status_s:
283+
total_count: int
284+
total_count_change: int
285+
last_policy_kind: rmw_qos_policy_kind_t
286+
287+
288+
rmw_qos_incompatible_event_status_t = rmw_qos_incompatible_event_status_s
289+
rmw_offered_qos_incompatible_event_status_t = rmw_qos_incompatible_event_status_t
290+
291+
292+
class RCLError(BaseException):
293+
def __init__(self, error_text: str) -> None: ...
294+
295+
296+
class UnsupportedEventTypeError(RCLError):
297+
pass
298+
299+
300+
class EventHandle(Destroyable, Generic[T]):
206301

207302
@overload
208-
def __init__(self, subcription: Subscription,
209-
event_type: rcl_subscription_event_type_t) -> None: ...
303+
def __init__(
304+
self,
305+
subcription: Subscription[Any],
306+
event_type: rcl_subscription_event_type_t
307+
) -> None: ...
210308

211309
@overload
212-
def __init__(self, publisher: Publisher, event_type: rcl_publisher_event_type_t) -> None: ...
310+
def __init__(
311+
self,
312+
subcription: Publisher[Any],
313+
event_type: rcl_publisher_event_type_t
314+
) -> None: ...
213315

214316
@property
215317
def pointer(self) -> int:
216318
"""Get the address of the entity as an integer."""
217319

218-
def take_event(self) -> Any | None:
320+
def take_event(self) -> T | None:
219321
"""Get pending data from a ready event."""
220322

221323

@@ -577,7 +679,7 @@ class WaitSet(Destroyable):
577679
def add_timer(self, timer: Timer) -> int:
578680
"""Add a timer to the wait set structure."""
579681

580-
def add_event(self, event: EventHandle) -> int:
682+
def add_event(self, event: EventHandle[Any]) -> int:
581683
"""Add an event to the wait set structure."""
582684

583685
def is_ready(self, entity_type: IsReadyValues, index: int) -> bool:

0 commit comments

Comments
 (0)