open-space-toolkit-astrodynamics 9.0.3__py39-none-manylinux2014_aarch64.whl → 13.0.2__py39-none-manylinux2014_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/METADATA +5 -2
- {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/RECORD +30 -28
- {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/WHEEL +1 -1
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so +0 -0
- ostk/astrodynamics/converters.py +36 -93
- ostk/astrodynamics/dataframe.py +479 -0
- ostk/astrodynamics/display.py +2 -0
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.13 +0 -0
- ostk/astrodynamics/pytrajectory/pystate.py +216 -1
- ostk/astrodynamics/test/conftest.py +2 -2
- ostk/astrodynamics/test/flight/test_maneuver.py +8 -12
- ostk/astrodynamics/test/flight/test_profile.py +155 -55
- ostk/astrodynamics/test/test_converters.py +43 -140
- ostk/astrodynamics/test/test_dataframe.py +875 -0
- ostk/astrodynamics/test/test_display.py +2 -3
- ostk/astrodynamics/test/test_event_condition.py +27 -7
- ostk/astrodynamics/test/test_trajectory.py +116 -38
- ostk/astrodynamics/test/test_utilities.py +31 -46
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +13 -0
- ostk/astrodynamics/test/trajectory/orbit/test_pass.py +9 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +3 -0
- ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +2 -2
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +48 -4
- ostk/astrodynamics/test/trajectory/test_orbit.py +42 -2
- ostk/astrodynamics/test/trajectory/test_segment.py +99 -1
- ostk/astrodynamics/test/trajectory/test_sequence.py +53 -0
- ostk/astrodynamics/test/trajectory/test_state.py +306 -0
- ostk/astrodynamics/utilities.py +125 -36
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.9 +0 -0
- {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/top_level.txt +0 -0
- {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/zip-safe +0 -0
@@ -11,8 +11,8 @@ from ostk.physics.time import Duration
|
|
11
11
|
from ostk.physics.coordinate import Frame
|
12
12
|
from ostk.physics.environment.object.celestial import Earth
|
13
13
|
|
14
|
-
from ostk.astrodynamics.flight import Maneuver
|
15
14
|
from ostk.astrodynamics.flight.system import SatelliteSystem
|
15
|
+
from ostk.astrodynamics import Dynamics
|
16
16
|
from ostk.astrodynamics.dynamics import CentralBodyGravity
|
17
17
|
from ostk.astrodynamics.dynamics import PositionDerivative
|
18
18
|
from ostk.astrodynamics.dynamics import Thruster
|
@@ -135,6 +135,104 @@ def thruster_dynamics() -> Thruster:
|
|
135
135
|
)
|
136
136
|
|
137
137
|
|
138
|
+
@pytest.fixture
|
139
|
+
def segment_solution(dynamics: list[Dynamics], state: State) -> Segment.Solution:
|
140
|
+
return Segment.Solution(
|
141
|
+
name="A Segment",
|
142
|
+
dynamics=dynamics,
|
143
|
+
states=[
|
144
|
+
state,
|
145
|
+
],
|
146
|
+
condition_is_satisfied=True,
|
147
|
+
segment_type=Segment.Type.Coast,
|
148
|
+
)
|
149
|
+
|
150
|
+
|
151
|
+
class TestSegmentSolution:
|
152
|
+
def test_constructors(
|
153
|
+
self,
|
154
|
+
segment_solution: Segment.Solution,
|
155
|
+
):
|
156
|
+
assert segment_solution is not None
|
157
|
+
assert segment_solution.name is not None
|
158
|
+
assert segment_solution.dynamics is not None
|
159
|
+
assert segment_solution.states is not None
|
160
|
+
assert segment_solution.condition_is_satisfied is not None
|
161
|
+
assert segment_solution.segment_type is not None
|
162
|
+
|
163
|
+
def test_getters_and_accessors(
|
164
|
+
self,
|
165
|
+
segment_solution: Segment.Solution,
|
166
|
+
):
|
167
|
+
assert segment_solution.access_start_instant() is not None
|
168
|
+
assert segment_solution.access_end_instant() is not None
|
169
|
+
assert segment_solution.get_interval() is not None
|
170
|
+
assert segment_solution.get_initial_mass() is not None
|
171
|
+
assert segment_solution.get_final_mass() is not None
|
172
|
+
assert segment_solution.get_propagation_duration() is not None
|
173
|
+
|
174
|
+
def test_compute_delta_v(
|
175
|
+
self,
|
176
|
+
segment_solution: Segment.Solution,
|
177
|
+
):
|
178
|
+
assert segment_solution.compute_delta_v(1500.0) is not None
|
179
|
+
|
180
|
+
def test_compute_delta_mass(
|
181
|
+
self,
|
182
|
+
segment_solution: Segment.Solution,
|
183
|
+
):
|
184
|
+
assert segment_solution.compute_delta_mass() is not None
|
185
|
+
|
186
|
+
@pytest.mark.skip(reason="Not implemented yet")
|
187
|
+
def test_extract_maneuvers(
|
188
|
+
self,
|
189
|
+
segment_solution: Segment.Solution,
|
190
|
+
):
|
191
|
+
assert segment_solution.extract_maneuvers(Frame.GCRF()) is not None
|
192
|
+
|
193
|
+
def test_calculate_states_at(
|
194
|
+
self,
|
195
|
+
segment_solution: Segment.Solution,
|
196
|
+
):
|
197
|
+
assert (
|
198
|
+
segment_solution.calculate_states_at(
|
199
|
+
[
|
200
|
+
segment_solution.states[0].get_instant(),
|
201
|
+
],
|
202
|
+
NumericalSolver.default_conditional(),
|
203
|
+
)
|
204
|
+
is not None
|
205
|
+
)
|
206
|
+
|
207
|
+
def get_dynamics_contribution(
|
208
|
+
self,
|
209
|
+
segment_solution: Segment.Solution,
|
210
|
+
):
|
211
|
+
assert (
|
212
|
+
segment_solution.get_dynamics_contribution(
|
213
|
+
segment_solution.dynamics[0], Frame.GCRF()
|
214
|
+
)
|
215
|
+
is not None
|
216
|
+
)
|
217
|
+
|
218
|
+
def get_dynamics_acceleration_contribution(
|
219
|
+
self,
|
220
|
+
segment_solution: Segment.Solution,
|
221
|
+
):
|
222
|
+
assert (
|
223
|
+
segment_solution.get_dynamics_acceleration_contribution(
|
224
|
+
segment_solution.dynamics[0], Frame.GCRF()
|
225
|
+
)
|
226
|
+
is not None
|
227
|
+
)
|
228
|
+
|
229
|
+
def get_all_dynamics_contributions(
|
230
|
+
self,
|
231
|
+
segment_solution: Segment.Solution,
|
232
|
+
):
|
233
|
+
assert segment_solution.get_all_dynamics_contributions(Frame.GCRF()) is not None
|
234
|
+
|
235
|
+
|
138
236
|
class TestSegment:
|
139
237
|
def test_get_name(self, coast_duration_segment: Segment, name: str):
|
140
238
|
assert coast_duration_segment.get_name() == name
|
@@ -339,6 +339,59 @@ def instants(state: State) -> list[Instant]:
|
|
339
339
|
return [state.get_instant(), state.get_instant() + Duration.minutes(1.0)]
|
340
340
|
|
341
341
|
|
342
|
+
@pytest.fixture
|
343
|
+
def sequence_solution(
|
344
|
+
segment_solution: Segment.Solution,
|
345
|
+
):
|
346
|
+
return Sequence.Solution(
|
347
|
+
segment_solutions=[
|
348
|
+
segment_solution,
|
349
|
+
],
|
350
|
+
execution_is_complete=True,
|
351
|
+
)
|
352
|
+
|
353
|
+
|
354
|
+
class TestSequenceSolution:
|
355
|
+
def test_properties(
|
356
|
+
self,
|
357
|
+
sequence_solution: Sequence.Solution,
|
358
|
+
):
|
359
|
+
assert sequence_solution is not None
|
360
|
+
assert len(sequence_solution.segment_solutions) == 1
|
361
|
+
assert sequence_solution.execution_is_complete
|
362
|
+
|
363
|
+
def test_getters_and_accessors(
|
364
|
+
self,
|
365
|
+
sequence_solution: Sequence.Solution,
|
366
|
+
):
|
367
|
+
assert sequence_solution.access_start_instant() is not None
|
368
|
+
assert sequence_solution.access_end_instant() is not None
|
369
|
+
|
370
|
+
assert sequence_solution.get_states() is not None
|
371
|
+
assert sequence_solution.get_initial_mass() is not None
|
372
|
+
assert sequence_solution.get_final_mass() is not None
|
373
|
+
assert sequence_solution.get_propagation_duration() is not None
|
374
|
+
|
375
|
+
assert sequence_solution.compute_delta_mass() is not None
|
376
|
+
assert sequence_solution.compute_delta_v(1500.0) is not None
|
377
|
+
|
378
|
+
def test_calculate_states_at(
|
379
|
+
self,
|
380
|
+
sequence_solution: Sequence.Solution,
|
381
|
+
numerical_solver: NumericalSolver,
|
382
|
+
):
|
383
|
+
instants: list[Instant] = sequence_solution.get_interval().generate_grid(
|
384
|
+
Duration.seconds(10.0)
|
385
|
+
)
|
386
|
+
states: list[State] = sequence_solution.calculate_states_at(
|
387
|
+
instants,
|
388
|
+
numerical_solver,
|
389
|
+
)
|
390
|
+
|
391
|
+
assert states is not None
|
392
|
+
assert len(states) == len(instants)
|
393
|
+
|
394
|
+
|
342
395
|
class TestSequence:
|
343
396
|
def test_get_segments(
|
344
397
|
self,
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
import pytest
|
4
4
|
|
5
|
+
from datetime import datetime, timezone
|
6
|
+
|
5
7
|
import numpy as np
|
6
8
|
|
7
9
|
from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
|
@@ -177,6 +179,310 @@ class TestState:
|
|
177
179
|
assert custom_state == state
|
178
180
|
assert custom_state is not state
|
179
181
|
|
182
|
+
def test_from_dict_with_eci_coordinates(self):
|
183
|
+
data = {
|
184
|
+
"timestamp": datetime.now(timezone.utc),
|
185
|
+
"rx_eci": 7000.0,
|
186
|
+
"ry_eci": 0.0,
|
187
|
+
"rz_eci": 0.0,
|
188
|
+
"vx_eci": 0.0,
|
189
|
+
"vy_eci": 7.5,
|
190
|
+
"vz_eci": 0.0,
|
191
|
+
}
|
192
|
+
|
193
|
+
state: State = State.from_dict(data)
|
194
|
+
|
195
|
+
assert state is not None
|
196
|
+
assert isinstance(state, State)
|
197
|
+
assert state.get_frame() == Frame.GCRF()
|
198
|
+
assert state.get_size() == 6
|
199
|
+
|
200
|
+
def test_from_dict_with_ecef_coordinates(self):
|
201
|
+
data = {
|
202
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
203
|
+
"rx_ecef": 7000.0,
|
204
|
+
"ry_ecef": 0.0,
|
205
|
+
"rz_ecef": 0.0,
|
206
|
+
"vx_ecef": 0.0,
|
207
|
+
"vy_ecef": 7.5,
|
208
|
+
"vz_ecef": 0.0,
|
209
|
+
}
|
210
|
+
|
211
|
+
state: State = State.from_dict(data)
|
212
|
+
|
213
|
+
assert state is not None
|
214
|
+
assert isinstance(state, State)
|
215
|
+
assert state.get_frame() == Frame.ITRF()
|
216
|
+
assert state.get_size() == 6
|
217
|
+
|
218
|
+
def test_from_dict_with_generic_coordinates(self):
|
219
|
+
data = {
|
220
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
221
|
+
"rx": 7000.0,
|
222
|
+
"ry": 0.0,
|
223
|
+
"rz": 0.0,
|
224
|
+
"vx": 0.0,
|
225
|
+
"vy": 7.5,
|
226
|
+
"vz": 0.0,
|
227
|
+
"frame": "GCRF",
|
228
|
+
}
|
229
|
+
|
230
|
+
state: State = State.from_dict(data)
|
231
|
+
|
232
|
+
assert state is not None
|
233
|
+
assert isinstance(state, State)
|
234
|
+
assert state.get_frame() == Frame.GCRF()
|
235
|
+
assert state.get_size() == 6
|
236
|
+
|
237
|
+
with pytest.raises(
|
238
|
+
ValueError, match="Frame must be provided for generic columns."
|
239
|
+
):
|
240
|
+
data = {
|
241
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
242
|
+
"rx": 7000.0,
|
243
|
+
"ry": 0.0,
|
244
|
+
"rz": 0.0,
|
245
|
+
"vx": 0.0,
|
246
|
+
"vy": 7.5,
|
247
|
+
"vz": 0.0,
|
248
|
+
}
|
249
|
+
|
250
|
+
State.from_dict(data)
|
251
|
+
|
252
|
+
with pytest.raises(ValueError, match="No frame exists with name \\[RANDOM\\]."):
|
253
|
+
data = {
|
254
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
255
|
+
"rx": 7000.0,
|
256
|
+
"ry": 0.0,
|
257
|
+
"rz": 0.0,
|
258
|
+
"vx": 0.0,
|
259
|
+
"vy": 7.5,
|
260
|
+
"vz": 0.0,
|
261
|
+
"frame": "RANDOM",
|
262
|
+
}
|
263
|
+
|
264
|
+
State.from_dict(data)
|
265
|
+
|
266
|
+
with pytest.raises(ValueError, match="Invalid frame data \\[123\\]"):
|
267
|
+
data = {
|
268
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
269
|
+
"rx": 7000.0,
|
270
|
+
"ry": 0.0,
|
271
|
+
"rz": 0.0,
|
272
|
+
"vx": 0.0,
|
273
|
+
"vy": 7.5,
|
274
|
+
"vz": 0.0,
|
275
|
+
"frame": 123,
|
276
|
+
}
|
277
|
+
|
278
|
+
State.from_dict(data)
|
279
|
+
|
280
|
+
def test_from_dict_with_attitude_quaternion(self):
|
281
|
+
data = {
|
282
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
283
|
+
"rx_eci": 7000.0,
|
284
|
+
"ry_eci": 0.0,
|
285
|
+
"rz_eci": 0.0,
|
286
|
+
"vx_eci": 0.0,
|
287
|
+
"vy_eci": 7.5,
|
288
|
+
"vz_eci": 0.0,
|
289
|
+
"q_B_ECI_x": 0.0,
|
290
|
+
"q_B_ECI_y": 0.0,
|
291
|
+
"q_B_ECI_z": 0.0,
|
292
|
+
"q_B_ECI_s": 1.0,
|
293
|
+
}
|
294
|
+
|
295
|
+
state: State = State.from_dict(data)
|
296
|
+
|
297
|
+
assert state is not None
|
298
|
+
assert isinstance(state, State)
|
299
|
+
assert state.get_frame() == Frame.GCRF()
|
300
|
+
assert state.get_size() == 10
|
301
|
+
|
302
|
+
def test_from_dict_with_angular_velocity(self):
|
303
|
+
data = {
|
304
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
305
|
+
"rx_eci": 7000.0,
|
306
|
+
"ry_eci": 0.0,
|
307
|
+
"rz_eci": 0.0,
|
308
|
+
"vx_eci": 0.0,
|
309
|
+
"vy_eci": 7.5,
|
310
|
+
"vz_eci": 0.0,
|
311
|
+
"w_B_ECI_in_B_x": 0.1,
|
312
|
+
"w_B_ECI_in_B_y": 0.2,
|
313
|
+
"w_B_ECI_in_B_z": 0.3,
|
314
|
+
}
|
315
|
+
|
316
|
+
state: State = State.from_dict(data)
|
317
|
+
|
318
|
+
assert state is not None
|
319
|
+
assert isinstance(state, State)
|
320
|
+
assert state.get_size() == 9
|
321
|
+
|
322
|
+
def test_from_dict_with_drag_coefficient(self):
|
323
|
+
data = {
|
324
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
325
|
+
"rx_eci": 7000.0,
|
326
|
+
"ry_eci": 0.0,
|
327
|
+
"rz_eci": 0.0,
|
328
|
+
"vx_eci": 0.0,
|
329
|
+
"vy_eci": 7.5,
|
330
|
+
"vz_eci": 0.0,
|
331
|
+
"drag_coefficient": 2.2,
|
332
|
+
}
|
333
|
+
|
334
|
+
state: State = State.from_dict(data)
|
335
|
+
|
336
|
+
assert state is not None
|
337
|
+
assert isinstance(state, State)
|
338
|
+
assert state.get_size() == 7
|
339
|
+
|
340
|
+
data = {
|
341
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
342
|
+
"rx_eci": 7000.0,
|
343
|
+
"ry_eci": 0.0,
|
344
|
+
"rz_eci": 0.0,
|
345
|
+
"vx_eci": 0.0,
|
346
|
+
"vy_eci": 7.5,
|
347
|
+
"vz_eci": 0.0,
|
348
|
+
"cd": 2.2,
|
349
|
+
}
|
350
|
+
|
351
|
+
state: State = State.from_dict(data)
|
352
|
+
|
353
|
+
assert state is not None
|
354
|
+
assert isinstance(state, State)
|
355
|
+
assert state.get_size() == 7
|
356
|
+
|
357
|
+
def test_from_dict_with_surface_area(self):
|
358
|
+
data = {
|
359
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
360
|
+
"rx_eci": 7000.0,
|
361
|
+
"ry_eci": 0.0,
|
362
|
+
"rz_eci": 0.0,
|
363
|
+
"vx_eci": 0.0,
|
364
|
+
"vy_eci": 7.5,
|
365
|
+
"vz_eci": 0.0,
|
366
|
+
"surface_area": 2.2,
|
367
|
+
}
|
368
|
+
|
369
|
+
state: State = State.from_dict(data)
|
370
|
+
|
371
|
+
assert state is not None
|
372
|
+
assert isinstance(state, State)
|
373
|
+
assert state.get_size() == 7
|
374
|
+
|
375
|
+
data = {
|
376
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
377
|
+
"rx_eci": 7000.0,
|
378
|
+
"ry_eci": 0.0,
|
379
|
+
"rz_eci": 0.0,
|
380
|
+
"vx_eci": 0.0,
|
381
|
+
"vy_eci": 7.5,
|
382
|
+
"vz_eci": 0.0,
|
383
|
+
"cross_sectional_area": 2.2,
|
384
|
+
}
|
385
|
+
|
386
|
+
state: State = State.from_dict(data)
|
387
|
+
|
388
|
+
assert state is not None
|
389
|
+
assert isinstance(state, State)
|
390
|
+
assert state.get_size() == 7
|
391
|
+
|
392
|
+
def test_from_dict_with_mass(self):
|
393
|
+
data = {
|
394
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
395
|
+
"rx_eci": 7000.0,
|
396
|
+
"ry_eci": 0.0,
|
397
|
+
"rz_eci": 0.0,
|
398
|
+
"vx_eci": 0.0,
|
399
|
+
"vy_eci": 7.5,
|
400
|
+
"vz_eci": 0.0,
|
401
|
+
"mass": 2.2,
|
402
|
+
}
|
403
|
+
|
404
|
+
state: State = State.from_dict(data)
|
405
|
+
|
406
|
+
assert state is not None
|
407
|
+
assert isinstance(state, State)
|
408
|
+
assert state.get_size() == 7
|
409
|
+
|
410
|
+
@pytest.mark.parametrize(
|
411
|
+
("data", "expected_length", "expected_frame"),
|
412
|
+
[
|
413
|
+
(
|
414
|
+
{
|
415
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
416
|
+
"r_GCRF_x": 1.0,
|
417
|
+
"r_GCRF_y": 2.0,
|
418
|
+
"r_GCRF_z": 3.0,
|
419
|
+
"v_GCRF_x": 4.0,
|
420
|
+
"v_GCRF_y": 5.0,
|
421
|
+
"v_GCRF_z": 6.0,
|
422
|
+
},
|
423
|
+
6,
|
424
|
+
Frame.GCRF(),
|
425
|
+
),
|
426
|
+
(
|
427
|
+
{
|
428
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
429
|
+
"r_ITRF_x": 1.0,
|
430
|
+
"r_ITRF_y": 2.0,
|
431
|
+
"r_ITRF_z": 3.0,
|
432
|
+
"v_ITRF_x": 4.0,
|
433
|
+
"v_ITRF_y": 5.0,
|
434
|
+
"v_ITRF_z": 6.0,
|
435
|
+
},
|
436
|
+
6,
|
437
|
+
Frame.ITRF(),
|
438
|
+
),
|
439
|
+
],
|
440
|
+
)
|
441
|
+
def test_from_dict_cannonical_success(
|
442
|
+
self, data: dict, expected_length: int, expected_frame: Frame
|
443
|
+
):
|
444
|
+
state: State = State.from_dict(data)
|
445
|
+
|
446
|
+
assert state is not None
|
447
|
+
assert isinstance(state, State)
|
448
|
+
|
449
|
+
assert state.get_size() == expected_length
|
450
|
+
assert state.get_frame() == expected_frame
|
451
|
+
|
452
|
+
@pytest.mark.parametrize(
|
453
|
+
("data", "expected_failure_message"),
|
454
|
+
[
|
455
|
+
(
|
456
|
+
{
|
457
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
458
|
+
"r_GCRF_x": 1.0,
|
459
|
+
"r_GCRF_y": 2.0,
|
460
|
+
"r_GCRF_z": 3.0,
|
461
|
+
"v_GCRF_x": 4.0,
|
462
|
+
"v_GCRF_y": 5.0,
|
463
|
+
},
|
464
|
+
"Invalid state data.",
|
465
|
+
),
|
466
|
+
(
|
467
|
+
{
|
468
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
469
|
+
"r_TEST_x": 1.0,
|
470
|
+
"r_TEST_y": 2.0,
|
471
|
+
"r_TEST_z": 3.0,
|
472
|
+
"v_TEST_x": 4.0,
|
473
|
+
"v_TEST_y": 5.0,
|
474
|
+
"v_TEST_z": 6.0,
|
475
|
+
},
|
476
|
+
"No frame exists with name \\[TEST\\].",
|
477
|
+
),
|
478
|
+
],
|
479
|
+
)
|
480
|
+
def test_from_dict_cannonical_failure(
|
481
|
+
self, data: dict, expected_failure_message: str
|
482
|
+
):
|
483
|
+
with pytest.raises(ValueError, match=expected_failure_message):
|
484
|
+
State.from_dict(data)
|
485
|
+
|
180
486
|
def test_comparators(self, state: State):
|
181
487
|
assert (state == state) is True
|
182
488
|
assert (state != state) is False
|