Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add python 3.12 to test #469

Merged
merged 8 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11']
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12']
name: Tests on ${{ matrix.python-version }}
steps:
- name: Setup python
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog/v3.2.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Bug Fixes
- Fix incorrect lap number (zero instead of one) for generated laps that are
added when a driver crashes on the very first lap.

- Fix broken schedule backend 'f1timing' (failed to load 2023 season schedule
after Qatar GP due to unexpected data)

- Fix: failing lap accuracy check for one driver caused laps of all drivers to
be marked as inaccurate (#464)


New Features
^^^^^^^^^^^^
Expand Down
46 changes: 40 additions & 6 deletions fastf1/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,11 @@ def _load_laps_data(self, livedata=None):
mask = pd.isna(result['LapStartTime']) & (~pd.isna(result['PitOutTime']))
result.loc[mask, 'LapStartTime'] = result.loc[mask, 'PitOutTime']

# remove first lap pitout time if it is before session_start_time
mask = (result["PitOutTime"] < self.session_start_time) & \
(result["NumberOfLaps"] == 1)
result.loc[mask, 'PitOutTime'] = pd.NaT

# create total laps counter for each tyre used
for npit in result['Stint'].unique():
sel = result['Stint'] == npit
Expand Down Expand Up @@ -1867,6 +1872,7 @@ def _check_lap_accuracy(self):
prev_lap = None
integrity_errors = 0
for _, lap in self.laps[self.laps['DriverNumber'] == drv].iterrows():
lap_integrity_ok = True
# require existence, non-existence and specific values for some variables
check_1 = (pd.isnull(lap['PitInTime'])
& pd.isnull(lap['PitOutTime'])
Expand All @@ -1884,7 +1890,7 @@ def _check_lap_accuracy(self):
lap['LapTime'].total_seconds(),
atol=0.003, rtol=0, equal_nan=False)
if not check_2:
integrity_errors += 1
lap_integrity_ok = False
else:
check_2 = False # data not available means fail

Expand All @@ -1894,16 +1900,44 @@ def _check_lap_accuracy(self):
else:
check_3 = True # no previous lap, no SC error

result = check_1 and check_2 and check_3
pre_check_4 = (((not pd.isnull(lap['Time']))
& (not pd.isnull(lap['LapTime'])))
and (prev_lap is not None)
and (not pd.isnull(prev_lap['Time'])))

if pre_check_4: # needed condition for check_4
time_diff = np.sum((lap['Time'],
-1 * prev_lap['Time'])).total_seconds()
lap_time = lap['LapTime'].total_seconds()
# If the difference between the two times is within a
# certain tolerance, the lap time data is considered
# to be valid.
check_4 = np.allclose(time_diff, lap_time,
atol=0.003, rtol=0, equal_nan=False)

if not check_4:
lap_integrity_ok = False

else:
check_4 = True

if not lap_integrity_ok:
integrity_errors += 1

result = check_1 and check_2 and check_3 and check_4
is_accurate.append(result)
prev_lap = lap

if len(is_accurate) > 0:
self._laps.loc[self.laps['DriverNumber'] == drv, 'IsAccurate'] = is_accurate
self._laps.loc[
self.laps['DriverNumber'] == drv, 'IsAccurate'
] = is_accurate
else:
_logger.warning("Failed to perform lap accuracy check - all "
"laps marked as inaccurate.")
self.laps['IsAccurate'] = False # default should be inaccurate
_logger.warning(f"Failed to perform lap accuracy check - all "
f"laps marked as inaccurate (driver {drv})")
self._laps.loc[
self.laps['DriverNumber'] == drv, 'IsAccurate'
] = False # default should be inaccurate

# necessary to explicitly cast to bool
self._laps[['IsAccurate']] \
Expand Down
26 changes: 19 additions & 7 deletions fastf1/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
identifier to differentiate between the various sessions of one event.
This identifier can currently be one of the following:

- session name abbreviation: ``'FP1', 'FP2', 'FP3', 'Q', 'S', 'SS', R'``
- session name abbreviation: ``'FP1', 'FP2', 'FP3', 'Q', 'S', 'SS', 'R'``
- full session name: ``'Practice 1', 'Practice 2',
'Practice 3', 'Sprint', 'Sprint Shootout', 'Qualifying', 'Race'``;
provided names will be normalized, so that the name is
Expand Down Expand Up @@ -607,15 +607,21 @@ def _get_schedule_from_f1_timing(year):
data['EventName'].append(event['Name'])
data['OfficialEventName'].append(event['OfficialName'])

n_events = min(len(event['Sessions']), 5)
# select only valid sessions
sessions = list()
for ses in event['Sessions']:
if (ses.get('Key') != -1) and ses.get('Name'):
sessions.append(ses)

n_events = min(len(sessions), 5)
# number of events, usually 3 for testing, 5 for race weekends
# in special cases there are additional unrelated events

if (n_events >= 4) and ('Sprint' in event['Sessions'][3]['Name']):
if event['Sessions'][3]['Name'] == 'Sprint Qualifying':
if (n_events >= 4) and ('Sprint' in sessions[3]['Name']):
if sessions[3]['Name'] == 'Sprint Qualifying':
# fix for 2021 where Sprint was called Sprint Qualifying
event['Sessions'][3]['Name'] = 'Sprint'
if event['Sessions'][2]['Name'] == 'Sprint Shootout':
sessions[3]['Name'] = 'Sprint'
if sessions[2]['Name'] == 'Sprint Shootout':
data['EventFormat'].append('sprint_shootout')
else:
data['EventFormat'].append('sprint')
Expand All @@ -632,7 +638,7 @@ def _get_schedule_from_f1_timing(year):
for i in range(0, 5):
# parse the up to five sessions for each event
try:
session = event['Sessions'][i]
session = sessions[i]
except IndexError:
data[f'Session{i+1}'].append(None)
data[f'Session{i+1}Date'].append(None)
Expand Down Expand Up @@ -730,6 +736,9 @@ def _get_schedule_from_ergast(year) -> "EventSchedule":
class EventSchedule(pd.DataFrame):
"""This class implements a per-season event schedule.

For detailed information about the information that is available for each
event, see `Event Schedule Data`_.

This class is usually not instantiated directly. You should use
:func:`fastf1.get_event_schedule` to get an event schedule for a specific
season.
Expand Down Expand Up @@ -928,6 +937,9 @@ class Event(pd.Series):
Each event consists of one or multiple sessions, depending on the type
of event and depending on the event format.

For detailed information about the information that is available for each
event, see `Event Schedule Data`_.

This class is usually not instantiated directly. You should use
:func:`fastf1.get_event` or similar to get a specific event.

Expand Down
17 changes: 17 additions & 0 deletions fastf1/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ def test_lap_data_loading_position_calculation():
assert (delta == 0).all() # assert that the delta is zero for all laps


@pytest.mark.f1telapi
def test_first_lap_pitout_times():
sprint_session = fastf1.get_session(2023, 4, "Sprint")
sprint_session.load(telemetry=False, weather=False, messages=False)
sprint_laps = sprint_session.laps
sprint_mask = (sprint_laps["LapNumber"] == 1) & \
(~sprint_laps["PitOutTime"].isna())
assert sprint_laps[sprint_mask]["Driver"].tolist() == ["OCO"]

race_session = fastf1.get_session(2023, 5, "R")
race_session.load(telemetry=False, weather=False, messages=False)
race_laps = race_session.laps
race_mask = (race_laps["LapNumber"] == 1) & \
(~race_laps["PitOutTime"].isna())
assert race_laps[race_mask]["Driver"].tolist() == []


def test_laps_constructor_metadata_propagation(reference_laps_data):
session, laps = reference_laps_data

Expand Down
7 changes: 7 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ filterwarnings =
# external, verified as not relevant
# (10/2022)
ignore:.*df.iloc.*will attempt to set the values inplace.*:FutureWarning
# external, datetime module deprecation
# (11/2023)
ignore:.*datetime\.datetime.utcnow\(\) is deprecated.*:DeprecationWarning
ignore:.*datetime\.datetime.utcfromtimestamp\(\) is deprecated.*:DeprecationWarning
# external, attribute removal planned for Python 3.14
# (11/2023)
ignore:.*Attribute s is deprecated and will be removed in Python 3.14.*:DeprecationWarning