From b1ecb4db989ee12bb7a272fc81db9b956be2eac2 Mon Sep 17 00:00:00 2001 From: Pratik Borole Date: Tue, 2 Apr 2024 19:41:02 +0530 Subject: [PATCH] feat: BACK-53 added /split-qualifying-laps path --- app/main.py | 112 ++++++++++++- app/models.py | 8 + tests/test_split_qualifying_laps.py | 236 ++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 tests/test_split_qualifying_laps.py diff --git a/app/main.py b/app/main.py index 8f04f04..a6a20d8 100644 --- a/app/main.py +++ b/app/main.py @@ -37,6 +37,7 @@ Results, Root, Schedule, + SplitQualifyingLaps, Standings, Telemetry, Weather, @@ -451,10 +452,10 @@ def get_laps( **NOTE**: - If `session` is not provided; we use the default session. Default = 5; ie race. - - If no `driver_numbers` are provided; you get laps for all drivers. + - If no `driver_number` are provided; you get laps for all drivers. **Returns**: - List[Laps]: Returns a JSON response with the list of session results + List[Laps]: Returns a JSON response with the list of laps """ try: @@ -486,6 +487,113 @@ def get_laps( ) +@app.get( + "/split-qualifying-laps/{year}/{round}", + tags=["laps"], + summary="Get split qualifying laps of one or more drivers for a given year and round", + response_description="Return split qualifying laps of one or more drivers for a given year and round.", + status_code=status.HTTP_200_OK, + response_model=SplitQualifyingLaps, + dependencies=[Depends(validate_token)], +) +def get_split_qualifying_laps( + year: Annotated[ + int, + Path( + title="The year for which to get the qualifying laps", + description="The year for which to get the qualifying laps", + ge=MIN_YEAR_SUPPORTED, + le=MAX_YEAR_SUPPORTED, + ), + ], + round: Annotated[ + int, + Path( + title="The round in a year for which to get the qualifying laps", + description="The round in a year for which to get the qualifying laps", + ge=MIN_ROUND_SUPPORTED, + le=MAX_ROUND_SUPPORTED, + ), + ], + driver_number: Annotated[ + List[int], + Query( + title="List of drivers for whom to get the qualifying laps", + description="List of drivers for whom to get the qualifying laps", + ), + ] = [], + # qualifying_session: Annotated[ + # List[int], + # Query( + # title="List of qualifying sessions for which to get the qualifying laps", + # description="List of qualifying sessions for which to get the qualifying laps", + # ), + # ] = [], +) -> SplitQualifyingLaps: + """ + ## Get split qualifying laps of one or more drivers for a given year and round + Endpoint to get split qualifying laps of one or more drivers for a given year and round. + + **NOTE**: + - If no `driver_number` are provided; you get laps for all drivers. + - If no `qualifying_session` are provided; you get laps for all qualifying sessions. + + **Returns**: + SplitQualifyingLaps: Returns a JSON response with the list of split qualifying laps + """ + + # valid_qualifying_sessions = {1, 2, 3} + # if not set(qualifying_session).issubset(valid_qualifying_sessions): + # raise HTTPException( + # status_code=status.HTTP_400_BAD_REQUEST, + # detail="Bad Request. Only values 1, 2, and 3 are allowed for qualifying_session.", + # ) + + try: + session_obj = fastf1.get_session(year=year, gp=round, identifier="Qualifying") + session_obj.load( + laps=True, + telemetry=False, + weather=False, + messages=True, # required for `Deleted` and `DeletedReason` + ) + session_laps = session_obj.laps + + if len(driver_number) > 0: + session_laps = session_laps.pick_drivers(driver_number) + + split_qualifying_laps = session_laps.split_qualifying_sessions() + + return SplitQualifyingLaps.model_validate_json( + json.dumps( + { + "Q1": ( + None + if split_qualifying_laps[0] is None + else json.loads(split_qualifying_laps[0].to_json(orient="records")) + ), + "Q2": ( + None + if split_qualifying_laps[1] is None + else json.loads(split_qualifying_laps[1].to_json(orient="records")) + ), + "Q3": ( + None + if split_qualifying_laps[2] is None + else json.loads(split_qualifying_laps[2].to_json(orient="records")) + ), + } + ) + ) + except ValueError as ve: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Bad Request. {str(ve)}") + except KeyError as ke: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Likely an error when fetching laps data for a session that has yet to happen. {str(ke)}", + ) + + @app.get( "/telemetry/{year}/{round}/{driver_number}/{lap}", tags=["telemetry"], diff --git a/app/models.py b/app/models.py index e42ce95..9b776ac 100644 --- a/app/models.py +++ b/app/models.py @@ -166,6 +166,14 @@ class Laps(BaseModel): IsAccurate: bool | None +class SplitQualifyingLaps(BaseModel): + """Response model for split qualifying laps""" + + Q1: List[Laps] | None + Q2: List[Laps] | None + Q3: List[Laps] | None + + class Telemetry(BaseModel): """Response model for session telemetry for a given year, round, session, driver and laps""" diff --git a/tests/test_split_qualifying_laps.py b/tests/test_split_qualifying_laps.py new file mode 100644 index 0000000..9383454 --- /dev/null +++ b/tests/test_split_qualifying_laps.py @@ -0,0 +1,236 @@ +# External +from fastapi import status + +# App +from . import client_with_auth + + +# region good inputs + + +def test_get_split_qualifying_laps(): + response = client_with_auth.get("/split-qualifying-laps/2023/6") + assert response.status_code == status.HTTP_200_OK + assert response.json()["Q1"][0] == { + "Time": 952796, + "Driver": "VER", + "DriverNumber": "1", + "LapTime": 109496, + "LapNumber": 1, + "Stint": 1, + "PitOutTime": 845753, + "PitInTime": None, + "Sector1Time": 40759, + "Sector2Time": 43892, + "Sector3Time": 24845, + "Sector1SessionTime": 884112, + "Sector2SessionTime": 927987, + "Sector3SessionTime": 952830, + "SpeedI1": 121, + "SpeedI2": 178, + "SpeedFL": 270, + "SpeedST": 270, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Red Bull Racing", + "LapStartTime": 845753, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + assert response.json()["Q2"][0] == { + "Time": 3020807, + "Driver": "VER", + "DriverNumber": "1", + "LapTime": None, + "LapNumber": 13, + "Stint": 3, + "PitOutTime": 2935323, + "PitInTime": None, + "Sector1Time": None, + "Sector2Time": 39705, + "Sector3Time": 20790, + "Sector1SessionTime": None, + "Sector2SessionTime": 3000041, + "Sector3SessionTime": 3020978, + "SpeedI1": 184, + "SpeedI2": 174, + "SpeedFL": 265, + "SpeedST": 261, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Red Bull Racing", + "LapStartTime": 2935323, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + assert response.json()["Q3"][0] == { + "Time": 4399675, + "Driver": "VER", + "DriverNumber": "1", + "LapTime": None, + "LapNumber": 22, + "Stint": 5, + "PitOutTime": 4314995, + "PitInTime": None, + "Sector1Time": None, + "Sector2Time": 38757, + "Sector3Time": 20605, + "Sector1SessionTime": None, + "Sector2SessionTime": 4379088, + "Sector3SessionTime": 4399869, + "SpeedI1": 182, + "SpeedI2": 184, + "SpeedFL": 265, + "SpeedST": 263, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Red Bull Racing", + "LapStartTime": 4314995, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + + +def test_get_split_qualifying_laps_with_driver_numbers(): + response = client_with_auth.get("/split-qualifying-laps/2023/6?driver_number=14&driver_number=44") + assert response.status_code == status.HTTP_200_OK + assert response.json()["Q1"][0] == { + "Time": 1009351, + "Driver": "ALO", + "DriverNumber": "14", + "LapTime": 89950, + "LapNumber": 1, + "Stint": 1, + "PitOutTime": 920127, + "PitInTime": None, + "Sector1Time": 24847, + "Sector2Time": 42205, + "Sector3Time": 22898, + "Sector1SessionTime": 944283, + "Sector2SessionTime": 986486, + "Sector3SessionTime": 1009517, + "SpeedI1": 182, + "SpeedI2": 173, + "SpeedFL": 268, + "SpeedST": 261, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Aston Martin", + "LapStartTime": 920127, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + assert response.json()["Q2"][0] == { + "Time": 3068521, + "Driver": "ALO", + "DriverNumber": "14", + "LapTime": 96499, + "LapNumber": 11, + "Stint": 3, + "PitOutTime": 2973745, + "PitInTime": None, + "Sector1Time": 29931, + "Sector2Time": 41348, + "Sector3Time": 25220, + "Sector1SessionTime": 3001953, + "Sector2SessionTime": 3043301, + "Sector3SessionTime": 3068521, + "SpeedI1": 178, + "SpeedI2": 181, + "SpeedFL": 268, + "SpeedST": 252, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Aston Martin", + "LapStartTime": 2972022, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + assert response.json()["Q3"][0] == { + "Time": 4425334, + "Driver": "ALO", + "DriverNumber": "14", + "LapTime": 96409, + "LapNumber": 20, + "Stint": 5, + "PitOutTime": 4331729, + "PitInTime": None, + "Sector1Time": 34209, + "Sector2Time": 40051, + "Sector3Time": 22149, + "Sector1SessionTime": 4363134, + "Sector2SessionTime": 4403185, + "Sector3SessionTime": 4425334, + "SpeedI1": 185, + "SpeedI2": 183, + "SpeedFL": 267, + "SpeedST": 260, + "IsPersonalBest": False, + "Compound": "SOFT", + "TyreLife": 1, + "FreshTyre": True, + "Team": "Aston Martin", + "LapStartTime": 4328925, + "LapStartDate": None, + "TrackStatus": "1", + "Position": None, + "Deleted": False, + "DeletedReason": "", + "FastF1Generated": False, + "IsAccurate": False, + } + + +# endregion good inputs + +# region bad inputs + + +def test_get_split_qualifying_laps_bad_driver_numbers(): + response = client_with_auth.get("/split-qualifying-laps/2023/6?driver_number=45&driver_number=83") + assert response.status_code == status.HTTP_200_OK + assert response.json() == {"Q1": None, "Q2": None, "Q3": None} + + +def test_get_split_qualifying_laps_bad_round(): + response = client_with_auth.get("/split-qualifying-laps/2023/24") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json() == {"detail": "Bad Request. Invalid round: 24"} + + +# endregion bad inputs