diff --git a/.github/workflows/selective_cache_persist.yml b/.github/workflows/selective_cache_persist.yml index b44e232bd..59e4e690b 100644 --- a/.github/workflows/selective_cache_persist.yml +++ b/.github/workflows/selective_cache_persist.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.8-minver', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: [ '3.9-minver', '3.9', '3.10', '3.11', '3.12'] # name: Persist cache for ${{ matrix.python-version }} steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 10dbe0105..883cbd0dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,10 +16,9 @@ jobs: matrix: include: - name-suffix: "(Minimum Versions)" - python-version: "3.8" + python-version: "3.9" cache-suffix: "-minver" extra-requirements: "-c requirements/minver.txt" - - python-version: "3.8" - python-version: "3.9" - python-version: "3.10" - python-version: "3.11" diff --git a/fastf1/_api.py b/fastf1/_api.py index e5e3aa5ba..1f7266462 100644 --- a/fastf1/_api.py +++ b/fastf1/_api.py @@ -4,7 +4,6 @@ import json import zlib from typing import ( - Dict, Optional, Union ) @@ -29,7 +28,7 @@ base_url = 'https://livetiming.formula1.com' -headers: Dict[str, str] = { +headers: dict[str, str] = { 'Host': 'livetiming.formula1.com', 'Connection': 'close', 'TE': 'identity', @@ -37,7 +36,7 @@ 'Accept-Encoding': 'gzip, identity', } -pages: Dict[str, str] = { +pages: dict[str, str] = { 'session_data': 'SessionData.json', # track + session status + lap count 'session_info': 'SessionInfo.jsonStream', # more rnd 'archive_status': 'ArchiveStatus.json', # rnd=1880327548 @@ -83,7 +82,7 @@ def make_path(wname, wdate, sname, sdate): # define all empty columns for timing data -EMPTY_LAPS = {'Time': pd.NaT, 'Driver': str(), 'LapTime': pd.NaT, +EMPTY_LAPS = {'Time': pd.NaT, 'Driver': '', 'LapTime': pd.NaT, 'NumberOfLaps': np.nan, 'NumberOfPitStops': np.nan, 'PitOutTime': pd.NaT, 'PitInTime': pd.NaT, 'Sector1Time': pd.NaT, 'Sector2Time': pd.NaT, @@ -92,7 +91,7 @@ def make_path(wname, wdate, sname, sdate): 'SpeedI1': np.nan, 'SpeedI2': np.nan, 'SpeedFL': np.nan, 'SpeedST': np.nan, 'IsPersonalBest': False} -EMPTY_STREAM = {'Time': pd.NaT, 'Driver': str(), 'Position': np.nan, +EMPTY_STREAM = {'Time': pd.NaT, 'Driver': '', 'Position': np.nan, 'GapToLeader': np.nan, 'IntervalToPositionAhead': np.nan} diff --git a/fastf1/core.py b/fastf1/core.py index e01eab58c..b2953987e 100644 --- a/fastf1/core.py +++ b/fastf1/core.py @@ -43,15 +43,13 @@ import re import typing import warnings +from collections.abc import Iterable from functools import cached_property from typing import ( Any, Callable, - Iterable, - List, Literal, Optional, - Tuple, Union ) @@ -925,8 +923,8 @@ def add_driver_ahead(self, drop_existing: bool = True) -> "Telemetry": ) if ((d['Date'].shape != dtd['Date'].shape) - or np.any((d['Date'].values - != dtd['Date'].values))): + or np.any(d['Date'].values + != dtd['Date'].values)): dtd = dtd.resample_channels(new_date_ref=d["Date"]) # indices need to match as .join works index-on-index @@ -1510,7 +1508,7 @@ def _load_laps_data(self, livedata=None): elif not len(d2): result = d1.copy() result.reset_index(drop=True, inplace=True) - result['Compound'] = str() + result['Compound'] = '' result['TyreLife'] = np.nan result['Stint'] = 0 result['New'] = False @@ -3298,7 +3296,7 @@ def pick_accurate(self) -> "Laps": """ return self[self['IsAccurate']] - def split_qualifying_sessions(self) -> List[Optional["Laps"]]: + def split_qualifying_sessions(self) -> list[Optional["Laps"]]: """Splits a lap object into individual laps objects for each qualifying session. @@ -3357,7 +3355,7 @@ def split_qualifying_sessions(self) -> List[Optional["Laps"]]: return laps def iterlaps(self, require: Optional[Iterable] = None) \ - -> Iterable[Tuple[int, "Lap"]]: + -> Iterable[tuple[int, "Lap"]]: """Iterator for iterating over all laps in self. This method wraps :meth:`pandas.DataFrame.iterrows`. @@ -3765,9 +3763,8 @@ class NoLapDataError(Exception): after processing the result. """ def __init__(self, *args): - super(NoLapDataError, self).__init__("Failed to load session because " - "the API did not provide any " - "usable data.") + super().__init__("Failed to load session because the API did not " + "provide any usable data.") class InvalidSessionError(Exception): @@ -3775,6 +3772,4 @@ class InvalidSessionError(Exception): can be found.""" def __init__(self, *args): - super(InvalidSessionError, self).__init__( - "No matching session can be found." - ) + super().__init__("No matching session can be found.") diff --git a/fastf1/ergast/interface.py b/fastf1/ergast/interface.py index ff3f00cbc..a157a8f43 100644 --- a/fastf1/ergast/interface.py +++ b/fastf1/ergast/interface.py @@ -1,10 +1,8 @@ import copy import json from typing import ( - List, Literal, Optional, - Type, Union ) @@ -252,7 +250,7 @@ class ErgastSimpleResponse(ErgastResponseMixin, ErgastResultFrame): _internal_names_set = set(_internal_names) @property - def _constructor(self) -> Type["ErgastResultFrame"]: + def _constructor(self) -> type["ErgastResultFrame"]: # drop from ErgastSimpleResponse to ErgastResultFrame, removing the # ErgastResponseMixin because a slice of the data is no longer a full # response and pagination, ... is therefore not supported anymore @@ -363,7 +361,7 @@ def description(self) -> ErgastResultFrame: return self._description @property - def content(self) -> List[ErgastResultFrame]: + def content(self) -> list[ErgastResultFrame]: """A ``list`` of :class:`ErgastResultFrame` that contain the main response data. diff --git a/fastf1/events.py b/fastf1/events.py index a88e3fff4..7100e69d1 100644 --- a/fastf1/events.py +++ b/fastf1/events.py @@ -194,7 +194,6 @@ from typing import ( Literal, Optional, - Type, Union ) @@ -633,7 +632,7 @@ def _get_schedule_ff1(year): data[f'session{j+1}_date'][i] = pd.Timestamp(date) data[f'session{j+1}_date_Utc'][i] = pd.Timestamp(date_utc) - str().capitalize() + ''.capitalize() df = pd.DataFrame(data) # change column names from snake_case to UpperCamelCase @@ -894,7 +893,7 @@ def __init__(self, *args, year: int = 0, self[col] = self[col].astype(_type) @property - def _constructor_sliced_horizontal(self) -> Type["Event"]: + def _constructor_sliced_horizontal(self) -> type["Event"]: return Event def is_testing(self): @@ -981,10 +980,10 @@ def _matcher_strings(ev): max_index = i if max_ratio != 100: - _logger.warning(( + _logger.warning( "Correcting user input " f"'{user_input}' to'{self.loc[max_index].EventName}'" - ) + ) return self.loc[max_index] diff --git a/fastf1/internals/pandas_extensions.py b/fastf1/internals/pandas_extensions.py index 9a5823c81..3188a5f03 100644 --- a/fastf1/internals/pandas_extensions.py +++ b/fastf1/internals/pandas_extensions.py @@ -1,5 +1,3 @@ -from typing import List - import numpy as np from pandas import ( DataFrame, @@ -35,7 +33,7 @@ def create_df_fast( *, - arrays: List[np.ndarray], + arrays: list[np.ndarray], columns: list, fallback: bool = True ) -> DataFrame: @@ -71,7 +69,7 @@ def create_df_fast( def _fallback_create_df( - arrays: List[np.ndarray], + arrays: list[np.ndarray], columns: list ) -> DataFrame: data = {col: arr for col, arr in zip(columns, arrays)} @@ -87,7 +85,7 @@ def _fallback_if_unsupported(func): @_fallback_if_unsupported def _unsafe_create_df_fast( - arrays: List[np.ndarray], + arrays: list[np.ndarray], columns: list ) -> DataFrame: # Implements parts of pandas' internal DataFrame creation mechanics diff --git a/fastf1/livetiming/__main__.py b/fastf1/livetiming/__main__.py index db2741c2d..bfd5e15a5 100644 --- a/fastf1/livetiming/__main__.py +++ b/fastf1/livetiming/__main__.py @@ -15,7 +15,7 @@ def save(args): def convert(args): - with open(args.input, 'r') as infile: + with open(args.input) as infile: messages = infile.readlines() data, ec = messages_from_raw(messages) with open(args.output, 'w') as outfile: diff --git a/fastf1/livetiming/client.py b/fastf1/livetiming/client.py index 4a845f682..6e006f9cd 100644 --- a/fastf1/livetiming/client.py +++ b/fastf1/livetiming/client.py @@ -3,10 +3,8 @@ import json import logging import time -from typing import ( - Iterable, - Optional -) +from collections.abc import Iterable +from typing import Optional import requests diff --git a/fastf1/livetiming/data.py b/fastf1/livetiming/data.py index 220b3e48e..f287674b1 100644 --- a/fastf1/livetiming/data.py +++ b/fastf1/livetiming/data.py @@ -95,7 +95,7 @@ def load(self): next_data = None else: # read a new file as next file - with open(next_file, 'r') as fobj: + with open(next_file) as fobj: next_data = fobj.readlines() if current_data is None: diff --git a/fastf1/req.py b/fastf1/req.py index d23a01f31..f16e0d6ec 100644 --- a/fastf1/req.py +++ b/fastf1/req.py @@ -29,10 +29,7 @@ import re import sys import time -from typing import ( - Optional, - Tuple -) +from typing import Optional import requests from requests_cache import CacheMixin @@ -524,8 +521,8 @@ def _enable_default_cache(cls): try: os.mkdir(cache_dir, mode=0o0700) except Exception as err: - _logger.error("Failed to create cache directory {0}. " - "Error {1}".format(cache_dir, err)) + _logger.error(f"Failed to create cache directory " + f"{cache_dir}. Error {err}") raise # Enable cache with default @@ -632,7 +629,7 @@ def ci_mode(cls, enabled: bool): cls._ci_mode = enabled @classmethod - def get_cache_info(cls) -> Tuple[Optional[str], Optional[int]]: + def get_cache_info(cls) -> tuple[Optional[str], Optional[int]]: """Returns information about the cache directory and its size. If the cache is not configured, None will be returned for both the @@ -658,7 +655,7 @@ def _convert_size(cls, size_bytes): # https://stackoverflow.com/questions/51940 i = int(math.floor(math.log(size_bytes, 1024))) p = math.pow(1024, i) s = round(size_bytes / p, 2) - return "%s %s" % (s, size_name[i]) + return f"{s} {size_name[i]}" @classmethod def _get_size(cls, start_path='.'): # https://stackoverflow.com/questions/1392413/calculating-a-directorys-size-using-python # noqa: E501 diff --git a/fastf1/utils.py b/fastf1/utils.py index fb2a02ea1..e04540bc7 100644 --- a/fastf1/utils.py +++ b/fastf1/utils.py @@ -3,9 +3,7 @@ import warnings from functools import reduce from typing import ( - Dict, Optional, - Tuple, Union ) @@ -22,7 +20,7 @@ def delta_time( reference_lap: "fastf1.core.Lap", compare_lap: "fastf1.core.Lap" -) -> Tuple[pd.Series, "fastf1.core.Telemetry", "fastf1.core.Telemetry"]: +) -> tuple[pd.Series, "fastf1.core.Telemetry", "fastf1.core.Telemetry"]: """Calculates the delta time of a given lap, along the 'Distance' axis of the reference lap. @@ -114,7 +112,7 @@ def mini_pro(stream): return delta, ref, comp -def recursive_dict_get(d: Dict, *keys: str, default_none: bool = False): +def recursive_dict_get(d: dict, *keys: str, default_none: bool = False): """Recursive dict get. Can take an arbitrary number of keys and returns an empty dict if any key does not exist. https://stackoverflow.com/a/28225747""" diff --git a/pyproject.toml b/pyproject.toml index 13b9d4c24..4c9488b47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,11 @@ readme = "README.md" license = { file = "LICENSE" } # minimum python version additionally needs to be changed in the test matrix -requires-python = ">=3.8" +requires-python = ">=3.9" # minimum package versions additionally need to be changed in requirements/minver.txt dependencies = [ "matplotlib>=3.5.1,<4.0.0", - "numpy>=1.21.5,<3.0.0", + "numpy>=1.23.1,<3.0.0", "pandas>=1.4.1,<3.0.0", "python-dateutil", "requests>=2.28.1", @@ -85,6 +85,7 @@ select = [ "E", "F", "W", + "UP", "NPY201" ] diff --git a/requirements/minver.txt b/requirements/minver.txt index 1f0f33cc4..ebb2a548b 100644 --- a/requirements/minver.txt +++ b/requirements/minver.txt @@ -1,5 +1,5 @@ matplotlib==3.5.1 -numpy==1.21.5 +numpy==1.23.1 pandas==1.4.1 requests==2.28.1 requests-cache==1.0.0