1"""
2Helper library to define type protocols for classes and functions in the project.
3
4Note:
5 That some types in the documentation have been simplified, e.g. some generics have been
6 omitted.
7
8See Also:
9 Useful references:
10 - https://docs.python.org/3/library/typing.html
11 - https://docs.python.org/3/library/stdtypes.html#types-genericalias
12 - https://typing-extensions.readthedocs.io/en/latest/
13"""
14
15# pyright: strict
16
17from __future__ import annotations
18
19import sys
20from typing import TYPE_CHECKING, Any, Callable, Hashable, Optional, Sequence, Union
21
22import carla # type: ignore
23from typing_extensions import Concatenate, Literal, ParamSpec, Protocol, TypeAlias, TypeAliasType, TypeVar
24
25if TYPE_CHECKING:
26 from agents.dynamic_planning.dynamic_local_planner import DynamicLocalPlanner # noqa: F401 ; used for typing
27 from agents.navigation.local_planner import LocalPlanner
28 from agents.tools.config_creation import AgentConfig, BehaviorAgentSettings, LunaticAgentSettings # noqa: F401
29 from classes.constants import AgentState
30 from classes.evaluation_function import ConditionFunction
31 from classes.rule import Context, Rule
32 from classes.worldmodel import WorldModel
33 from classes.ui.keyboard_controls import KeyboardControl
34
35__all__ = [ # noqa: RUF022
36 "AgentConfigT",
37 "RuleT",
38 "CallableT",
39 # Callables
40 "CallableCondition",
41 "CallableConditionT",
42 "AnyCallableCondition",
43 # ConditionFunction
44 "ConditionFunctionLike",
45 "ConditionFunctionLikeT",
46 "AnyConditionFunctionLike",
47 "AnyConditionFunctionLikeT",
48 # Action
49 "CallableAction",
50 "CallableActionT",
51 "AnyCallableAction",
52 # Protocols
53 "HasBaseSettings",
54 "HasConfig",
55 "HasContext",
56 "Has_WorldModel",
57 "HasStates",
58 "Has_Vehicle",
59 "HasPlanner",
60 "HasPlannerWithConfig",
61 "UseableWithDynamicPlanner",
62 "CanDetectObstacles",
63 "CanDetectNearbyObstacles",
64 "CanDetectNearbyTrafficLights",
65]
66
67_T = TypeVar("_T", default=Any)
68_CH = TypeVar("_CH", bound=Hashable, default=Hashable) # Generic of ConditionFunction
69
70_P = ParamSpec("_P", default=[]) # Free, e.g. for action function.
71_CP = ParamSpec("_CP", default=[]) # Generic of ConditionFunction
72
73RuleT = TypeVar("RuleT", bound="Rule", default="Rule")
74"""A :py:class:`typing.TypeVar`: for a :py:class:`.Rule` type."""
75
76_A = TypeVar("_A", bound=carla.Actor, default=carla.Actor)
77
78# NOTE: This requires a stub file where ActorList is Generic
79if TYPE_CHECKING:
80 _Generic_carlaActorList = carla.ActorList[_A]
81else:
82 _Generic_carlaActorList = TypeAliasType("_Generic_carlaActorList", carla.ActorList, type_params=(_A,))
83
84ActorList: TypeAlias = Union[_Generic_carlaActorList[_A], Sequence[_A]]
85"""Type alias for a sequence of carla actors."""
86
87ControllerClassT = TypeVar("ControllerClassT", bound="KeyboardControl")
88"""A :py:class:`typing.TypeVar`: for a :py:class:`.KeyboardControl` type."""
89
90
91CallableCondition: TypeAlias = Union[
92 Callable[Concatenate[RuleT, "Context", _CP], _CH], # With Rule
93 Callable[Concatenate["Context", _CP], _CH], # Only Context
94]
95"""
96A :term:`generic type` alias for a callable condition function to be used with a :py:class:`.ConditionFunction`.
97Its first arguments must accept a :py:class:`.Rule` and a :py:class:`.Context`,
98or only a :py:class:`.Context`, additional keyword arguments are allowed.
99It must return a :term:`hashable` value.
100"""
101
102CallableAction: TypeAlias = Union[
103 Callable[Concatenate[RuleT, "Context", _P], _T], # With Rule
104 Callable[Concatenate["Context", _P], _T], # Only Context
105]
106"""
107A :term:`generic type` alias for a callable action function to be used with a :py:class:`.Rule`
108or :py:meth:`.ConditionFunction.register_action`.
109Its first arguments must accept a :py:class:`.Rule` and a :py:class:`.Context`,
110or only a :py:class:`.Context`, additional keyword arguments are allowed.
111It can return an arbitrary value.
112"""
113
114
115CallableT = TypeVar("CallableT", bound=Callable[..., Any])
116"""A :py:class:`typing.TypeVar`: for a any callable."""
117
118AgentConfigT = TypeVar("AgentConfigT", bound="AgentConfig", default="AgentConfig", infer_variance=True)
119"""A :py:class:`typing.TypeVar`: for a :py:class:`.AgentConfig` type."""
120
121ConditionFunctionLike = TypeAliasType(
122 "ConditionFunctionLike",
123 Union[CallableCondition[RuleT, _CP, _CH], "ConditionFunction[_CP, _CH]"],
124 type_params=(RuleT, _CP, _CH),
125)
126"""
127Callable that can be used for :py:attr:`.Rule.condition`.
128A callable that uses a :py:class:`Context` object as a single argument,
129or alternatively a :py:class:`Rule` and a :py:class:`Context` object (in this order).
130
131The function must return a :term:`Hashable` value.
132"""
133
134ConditionFunctionLikeT = ... # Version limitation, fixing done below
135""":py:class:`.TypeVar` version of :py:obj:`ConditionFunctionLike`"""
136
137
138AnyConditionFunctionLike = TypeAliasType(
139 "AnyConditionFunctionLike",
140 Union[CallableCondition[RuleT, _CP, _CH], "ConditionFunction[_CP, _CH]"],
141 type_params=(RuleT, _CP, _CH),
142)
143"""
144A :term:`generic type` alias for a callable condition function to be used with a :py:class:`.Rule`.
145Its first arguments must accept a :py:class:`.Rule` and a :py:class:`.Context`,
146or only a :py:class:`.Context`, additional keyword arguments are allowed.
147It must return a :term:`hashable` value.
148"""
149
150AnyConditionFunctionLikeT = TypeVar("AnyConditionFunctionLikeT", bound=AnyConditionFunctionLike)
151
152# Python 3.11+
153if TYPE_CHECKING or sys.version_info >= (3, 11):
154 AnyCallableCondition: TypeAlias = CallableCondition[RuleT, ..., Hashable]
155 """Non generic variant of :py:obj:`CallableCondition`, can use used as :py:class:`typing.TypeAlias`."""
156
157 AnyCallableAction: TypeAlias = CallableAction[RuleT, ..., Any]
158 """Non generic variant of :py:obj:`CallableAction`, can use used as :py:class:`typing.TypeAlias`."""
159
160 ConditionFunctionLikeT = TypeVar("ConditionFunctionLikeT", bound=ConditionFunctionLike["Rule", ..., Hashable])
161 """:py:class:`.TypeVar` version of :py:obj:`ConditionFunctionLike`"""
162
163# handle version conflicts with ParamSpec, Concatenate and Ellipsis
164# NOTE: Near-Future typing_extension upgrades should make this easier.
165elif sys.version_info[:2] <= (3, 10):
166 __ellipsis_dummy = ParamSpec("__ellipsis_dummy")
167 # Create valid concatenate types
168 __ConcatRC = Concatenate["Rule", "Context", __ellipsis_dummy]
169 __ConcatC = Concatenate["Context", __ellipsis_dummy]
170
171 AnyCallableAction = CallableAction["Rule", __ellipsis_dummy, Any]
172 AnyCallableCondition = CallableCondition["Rule", __ellipsis_dummy, Hashable]
173
174 __ConditionFunctionLikeBound = ConditionFunctionLike["Rule", __ellipsis_dummy, Hashable]
175
176 # Remove dummy parameter
177 __ConcatRC.__args__ = (*__ConcatRC.__args__[:-1], ...)
178 __ConcatC.__args__ = (*__ConcatC.__args__[:-1], ...)
179 __ConditionFunctionLikeBound.__args__ = tuple(
180 a if a is not __ellipsis_dummy else ... for a in __ConditionFunctionLikeBound.__args__
181 )
182 AnyCallableCondition.__args__[0].__args__ = (__ConcatRC, Hashable)
183 AnyCallableCondition.__args__[1].__args__ = (__ConcatC, Hashable)
184 AnyCallableAction.__args__[0].__args__ = (__ConcatRC, Any)
185 AnyCallableAction.__args__[1].__args__ = (__ConcatC, Any)
186 AnyCallableCondition.__parameters__ = AnyCallableCondition.__args__[
187 0
188 ].__parameters__ = AnyCallableCondition.__args__[1].__parameters__ = (
189 AnyCallableAction.__parameters__
190 ) = AnyCallableAction.__args__[0].__parameters__ = AnyCallableAction.__args__[1].__parameters__ = (
191 __ConditionFunctionLikeBound.__parameters__
192 ) = ()
193
194 ConditionFunctionLikeT = TypeVar("ConditionFunctionLikeT", bound=__ConditionFunctionLikeBound)
195else:
196 # Works for older typing_extensions versions, e.g. 4.10.0 and Python3.10
197 AnyCallableCondition = CallableCondition["Rule", Concatenate[...], Hashable]
198 AnyCallableAction = CallableAction["Rule", Concatenate[...], Any]
199
200CallableConditionT = TypeVar("CallableConditionT", bound=AnyCallableCondition)
201""":py:class:`typing.TypeVar` variant of :py:obj:`AnyCallableCondition`."""
202
203CallableActionT = TypeVar("CallableActionT", bound=AnyCallableAction)
204""":py:class:`typing.TypeVar` variant of :py:obj:`AnyCallableAction`."""
205
206
207# ------------- Protocols -------------
208
209
[docs]
210class HasBaseSettings(Protocol[AgentConfigT]):
211 BASE_SETTINGS: type[AgentConfigT]
212
213
[docs]
214class HasConfig(Protocol[AgentConfigT]):
215 @property # Note: Must be read-only, can be normal attribute when implemented
216 def config(self) -> AgentConfigT:
217 """
218 read-only attribute of a :py:class:`.AgentConfig` object; can also be a normal attribute.
219 """
220 ...
221
222
223LocalPlannerT = TypeVar(
224 "LocalPlannerT", bound="LocalPlanner", default="LocalPlanner", infer_variance=True
225) # is covariant, but avoid _co style for documentation
226"""A :py:class:`typing.TypeVar`: for a :py:class:`.LocalPlanner` type."""
227
228
[docs]
229class HasPlanner(Protocol[LocalPlannerT]):
230 """
231 Uses a Local planner to calculate controls
232 """
233
234 @property
235 def _local_planner(self) -> LocalPlannerT:
236 """
237 read-only attribute for a :py:class:`.LocalPlanner` object; can also be a normal attribute.
238
239 :meta public:
240 """
241 ...
242
[docs]
243 def _calculate_control(self, debug: bool = False, *args, **kwargs) -> carla.VehicleControl: # pyright: ignore
244 """
245 :meta public:
246 """
247 ...
248
249
[docs]
250class HasPlannerWithConfig(HasPlanner["DynamicLocalPlanner"], HasConfig[AgentConfigT], Protocol):
251 """
252 Uses a :py:class:`.DynamicLocalPlanner` that works with a :py:class:`.AgentConfig`
253 """
254
255
[docs]
256class HasContext(Protocol):
257 ctx: "Context"
258
259
[docs]
260class Has_WorldModel(Protocol):
261 _world_model: "WorldModel"
262 """
263 :meta public:
264 """
265
266
[docs]
267class HasStates(Protocol):
268 current_states: dict["AgentState", int]
269
270
[docs]
271class Has_Vehicle(Protocol):
272 _vehicle: carla.Vehicle
273 """
274 :meta public:
275 """
276
277
[docs]
278class UseableWithDynamicPlanner(HasPlannerWithConfig, Has_Vehicle, HasContext, Protocol):
279 """Can be used with :py:class:`.DynamicLocalPlanner`."""
280
281
[docs]
282class CanDetectObstacles(Has_Vehicle, HasPlanner, HasConfig["BehaviorAgentSettings | LunaticAgentSettings"], Protocol):
283 """Can be used with :py:func:`lunatic_agent_tools.detect_obstacles`."""
284
285
[docs]
286class CanDetectNearbyObstacles(CanDetectObstacles, Protocol):
287 """Can be used with :py:func:`lunatic_agent_tools.detect_obstacles_in_path`."""
288
289 all_obstacles_nearby: list[carla.Actor]
290 """Actors that are considered to be near the actor."""
291
[docs]
292 def max_detection_distance(self, lane: Literal["same_lane", "other_lane"]) -> float:
293 """
294 Convenience function to be used with :py:func:`lunatic_agent_tools.detect_vehicles`
295 and :any:`LunaticAgent.detect_obstacles_in_path`.
296
297 The max distance to consider an obstacle in the same lane or in another lane, obstacles
298 further away will be ignored.
299
300 Parameters:
301 self : An object that implements the `config` and `live_info` attributes
302 lane : The lane to consider.
303 """
304 ...
305
306
[docs]
307class CanDetectNearbyTrafficLights(CanDetectObstacles, HasStates, Has_WorldModel, Protocol):
308 """Can be used with :py:func:`lunatic_agent_tools.detect_obstacles_in_path`."""
309
310 traffic_lights_nearby: list[carla.TrafficLight]
311 """Actors that are considered to be near the actor."""
312
313 _last_traffic_light: Optional[carla.TrafficLight]
314 """
315 Last traffic light that was detected. At a red traffic light it can be checked if this is still
316 red.
317
318 Attention:
319 Not necessarily the same as :py:attr:`.InformationManager.Information.relevant_traffic_light`.
320
321 :meta public:
322 """
323
324 _current_waypoint: carla.Waypoint
325 """
326 :meta public:
327 """