Skip to content

Commit

Permalink
feat: BACK-53 added /split-qualifying-laps path
Browse files Browse the repository at this point in the history
  • Loading branch information
borolepratik committed Apr 2, 2024
1 parent 4192c12 commit b1ecb4d
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 2 deletions.
112 changes: 110 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
Results,
Root,
Schedule,
SplitQualifyingLaps,
Standings,
Telemetry,
Weather,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"],
Expand Down
8 changes: 8 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down
236 changes: 236 additions & 0 deletions tests/test_split_qualifying_laps.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit b1ecb4d

Please sign in to comment.