Skip to content

Commit fa55a62

Browse files
committed
ENH: correction and update of verify trigger and action methods and implementation of new tests, mostly for verify actions method.
1 parent bc80a1f commit fa55a62

3 files changed

Lines changed: 133 additions & 16 deletions

File tree

rocketpy/simulation/events.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,12 @@ def __verify_trigger(self, trigger):
123123
(at least if not declared or annotated).
124124
"""
125125
# verify if the trigger function accepts only **kwargs arguments
126+
# also avoids functions with no arguments, since they can't be used as triggers
126127
s = inspect.signature(trigger)
127-
if any(p.kind != inspect.Parameter.VAR_KEYWORD for p in s.parameters.values()):
128+
if (
129+
any(p.kind != inspect.Parameter.VAR_KEYWORD for p in s.parameters.values())
130+
or len(s.parameters) == 0
131+
):
128132
raise ValueError(
129133
f"The Trigger function of the {self.name} event must accept only keyword arguments. def {trigger.__name__}(**kwargs) -> bool:"
130134
)
@@ -159,20 +163,25 @@ def __verify_action(self, action):
159163
"""
160164
# verify if the action function accepts only **kwargs arguments
161165
s = inspect.signature(action)
162-
if any(p.kind != inspect.Parameter.VAR_KEYWORD for p in s.parameters.values()):
166+
if (
167+
any(p.kind != inspect.Parameter.VAR_KEYWORD for p in s.parameters.values())
168+
or len(s.parameters) == 0
169+
):
163170
raise ValueError(
164-
f"The Action function of the {self.name} event must accept only keyword arguments. def {action.__name__}(**kwargs) -> None or dict:"
171+
f"The Action function of the {self.name} event must accept only keyword arguments. def {action.__name__}(**kwargs) -> None | dict:"
165172
)
166173
# verify if the return type annotation is None or dict
167174
# Since is not possible to know for sure if the user is actually returning None or a dict,
168175
# we enforce None or dict annotation to motivate users to actually return None or dict.
169-
return_annotation = get_type_hints(action).get("return", None)
170-
if return_annotation is not None and return_annotation is not (
171-
type(None) or dict
176+
return_annotation = get_type_hints(action).get("return", int)
177+
if (
178+
(return_annotation is not type(None))
179+
and (return_annotation is not dict)
180+
and (return_annotation is not bool)
172181
):
173182
raise ValueError(
174183
f"The Action function of the {self.name} event must return None or a dictionary and must be annotated with '-> None' or '-> dict' for type checking.\n"
175-
f"def {action.__name__}(**kwargs) -> None or dict:"
184+
f"def {action.__name__}(**kwargs) -> None | dict:"
176185
)
177186
return action
178187

rocketpy/simulation/flight.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,10 +1048,10 @@ def __check_simulation_events(self, phase, phase_index, node_index):
10481048

10491049
return False
10501050

1051-
def __handle_out_of_rail_event(self, **kwargs):
1051+
def __handle_out_of_rail_event(self, **kwargs) -> bool:
10521052
"""Handle the out of rail event.
10531053
1054-
Parameters
1054+
keyword arguments are passed by the Event class when the trigger function is called.
10551055
----------
10561056
phase : FlightPhase
10571057
The current flight phase.
@@ -1065,9 +1065,9 @@ def __handle_out_of_rail_event(self, **kwargs):
10651065
bool
10661066
True to indicate the simulation should break.
10671067
"""
1068-
phase = kwargs.get("phase")
1069-
phase_index = kwargs.get("phase_index")
1070-
node_index = kwargs.get("node_index")
1068+
phase = kwargs["phase"]
1069+
phase_index = kwargs["phase_index"]
1070+
node_index = kwargs["node_index"]
10711071
# Check exactly when it went out using root finding
10721072
# Disconsider elevation
10731073
self.solution[-2][3] -= self.env.elevation

tests/unit/simulation/test_events.py

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,35 @@ def test_verify_trigger_accepts_only_kwargs():
77
def trigger(**kwargs) -> bool:
88
return True
99

10-
def action(**kwargs):
10+
def action(**kwargs) -> None:
1111
return None
1212

1313
event = Event(trigger=trigger, action=action, name="test")
1414
assert event.trigger is trigger
1515

1616

17+
def test_verify_trigger_evaluation_of_number_of_parameters():
18+
def trigger(**kwargs) -> bool:
19+
a = kwargs["a"]
20+
b = kwargs["b"]
21+
c = kwargs["c"]
22+
return a + b + c == 6
23+
24+
def action(**kwargs) -> None:
25+
return None
26+
27+
kwargs_test = {"a": 1, "b": 2, "c": 3}
28+
assert trigger(**kwargs_test)
29+
30+
event = Event(trigger=trigger, action=action, name="test")
31+
assert event.trigger is trigger
32+
33+
1734
def test_verify_trigger_rejects_missing_kwargs():
1835
def trigger(a, b) -> bool:
1936
return True
2037

21-
def action(**kwargs):
38+
def action(**kwargs) -> None:
2239
return None
2340

2441
with pytest.raises(
@@ -32,7 +49,21 @@ def test_verify_trigger_rejects_args_with_kwargs():
3249
def trigger(a, b, **kwargs) -> bool:
3350
return True
3451

35-
def action(**kwargs):
52+
def action(**kwargs) -> None:
53+
return None
54+
55+
with pytest.raises(
56+
ValueError,
57+
match=r"The Trigger function of the test event must accept only keyword arguments. def trigger\(\*\*kwargs\) -> bool:",
58+
):
59+
Event(trigger=trigger, action=action, name="test")
60+
61+
62+
def test_verify_trigger_rejects_triggers_with_no_parameters():
63+
def trigger() -> bool:
64+
return True
65+
66+
def action(**kwargs) -> None:
3667
return None
3768

3869
with pytest.raises(
@@ -46,7 +77,7 @@ def test_verify_trigger_rejects_triggers_without_bool_return_annotation():
4677
def trigger(**kwargs):
4778
return True
4879

49-
def action(**kwargs):
80+
def action(**kwargs) -> None:
5081
return None
5182

5283
with pytest.raises(
@@ -55,3 +86,80 @@ def action(**kwargs):
5586
+ r"def trigger\(\*\*kwargs\) -> bool\:",
5687
):
5788
Event(trigger=trigger, action=action, name="test")
89+
90+
91+
# The following tests verify if action functions were correctly implemented
92+
93+
94+
def test_verify_action_accepts_only_kwargs():
95+
def trigger(**kwargs) -> bool:
96+
return True
97+
98+
def action(**kwargs) -> None:
99+
return None
100+
101+
event = Event(trigger=trigger, action=action, name="test")
102+
assert event.action is action
103+
104+
105+
def test_verify_action_rejects_missing_kwargs():
106+
def trigger(**kwargs) -> bool:
107+
return True
108+
109+
def action(a, b) -> None:
110+
return None
111+
112+
with pytest.raises(
113+
ValueError,
114+
match=r"The Action function of the test event must accept only keyword arguments. def action\(\*\*kwargs\) -> None \| dict:",
115+
):
116+
Event(trigger=trigger, action=action, name="test")
117+
118+
119+
def test_verify_action_rejects_args_with_kwargs():
120+
def trigger(**kwargs) -> bool:
121+
return True
122+
123+
def action(a, b, **kwargs) -> None:
124+
return None
125+
126+
with pytest.raises(
127+
ValueError,
128+
match=r"The Action function of the test event must accept only keyword arguments. def action\(\*\*kwargs\) -> None \| dict:",
129+
):
130+
Event(trigger=trigger, action=action, name="test")
131+
132+
133+
def test_verify_action_accepts_dict_return_type():
134+
def trigger(**kwargs) -> bool:
135+
return True
136+
137+
def action(**kwargs) -> dict:
138+
return {"key": "value"}
139+
140+
event = Event(trigger=trigger, action=action, name="test")
141+
assert event.action is action
142+
143+
144+
def test_verify_action_accepts_none_return_type():
145+
def trigger(**kwargs) -> bool:
146+
return True
147+
148+
def action(**kwargs) -> None:
149+
return None
150+
151+
event = Event(trigger=trigger, action=action, name="test")
152+
assert event.action is action
153+
154+
155+
# this was also allowed because some actions functions already return bool, they need to be updated
156+
# then this test can be removed and the check for bool return type can be removed from the events.py file
157+
def test_verify_action_accepts_bool_return_type():
158+
def trigger(**kwargs) -> bool:
159+
return True
160+
161+
def action(**kwargs) -> bool:
162+
return True
163+
164+
event = Event(trigger=trigger, action=action, name="test")
165+
assert event.action is action

0 commit comments

Comments
 (0)