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