From 92aa85fe8d84a1736ac7f6248386c851a9e6f25c Mon Sep 17 00:00:00 2001 From: Philipp Schaefer <23384863+theOehrly@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:08:39 +0100 Subject: [PATCH] MNT: switch to pyproject.toml, PEP-518 build system and improve CI (#498) --- .github/workflows/docs.yml | 13 +- .github/workflows/release.yml | 20 +- .github/workflows/selective_cache_persist.yml | 2 +- .github/workflows/semver_test.yml | 25 ++ .github/workflows/tests.yml | 94 +++---- .gitignore | 4 + .pre-commit-config.yaml | 14 +- LICENSE | 2 +- MANIFEST.in | 1 - README.md | 8 +- conftest.py | 9 - docs/Makefile | 3 + docs/conf.py | 11 +- docs/contributing/coding_guide.rst | 4 +- docs/contributing/contributing.rst | 45 ++-- docs/contributing/devenv_setup.rst | 6 +- docs/contributing/documenting_fastf1.rst | 16 +- docs/make.bat | 4 + fastf1/__init__.py | 16 +- fastf1/ergast/interface.py | 4 +- fastf1/ergast/sphinx.py | 2 +- fastf1/events.py | 2 +- fastf1/mvapi/api.py | 5 +- fastf1/version.py | 1 - pyproject.toml | 89 +++++++ pytest.ini | 2 - requirements-dev.txt => requirements/dev.txt | 22 +- requirements/minver.txt | 8 + scripts/flake8_line_length.py | 18 -- scripts/visualization.py | 234 ------------------ setup.cfg | 49 ---- setup.py | 15 -- 32 files changed, 274 insertions(+), 474 deletions(-) create mode 100644 .github/workflows/semver_test.yml delete mode 100644 MANIFEST.in delete mode 100644 fastf1/version.py create mode 100644 pyproject.toml rename requirements-dev.txt => requirements/dev.txt (74%) create mode 100644 requirements/minver.txt delete mode 100644 scripts/flake8_line_length.py delete mode 100644 scripts/visualization.py delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 77f7b0394..65537a8f5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,20 +22,23 @@ jobs: python-version: '3.9' - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch the complete repo history (for setuptools-scm) - name: Cache pip uses: actions/cache@v3 with: path: ~/.cache/pip - key: pip-cache-${{ hashFiles('requirements/*/*.txt') }} + key: pip-cache-${{ hashFiles('requirements/*.txt') }} restore-keys: | pip-cache - name: Install python requirements run: | - python3 -m pip install --upgrade pip setuptools wheel - python3 -m pip install -r requirements-dev.txt + python -m pip install --upgrade pip + python -m pip install --upgrade build twine + python -m pip install -r requirements/dev.txt - name: Create cache directory run: | @@ -51,7 +54,7 @@ jobs: - name: Install Fast-F1 from sources run: | - python3 -m pip install -e . + python -m pip install -e . - name: Create doc build dir run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 439f5edc0..af3b6e04c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,14 +10,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch the complete repo history (for setuptools-scm) - uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + python -m pip install --upgrade build twine # if this is a release, upload to PyPI - name: Build and publish release @@ -26,15 +28,21 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: | - python setup.py sdist bdist_wheel + PACKAGE_VERSION=$(hatch version) + echo "Creating package with version $PACKAGE_VERSION" + python -m build twine upload dist/* - # if this is a manual dispatch, upload to TestPyPI + # if this is a manual dispatch, upload to PyPI test index - name: Build and publish test release if: github.event_name == 'workflow_dispatch' env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN}} + # override setuptools_scm config to remove local version, else PyPI will not accept the package + # this allows to push development releases to the test index from any repository state run: | - python setup.py sdist bdist_wheel + PACKAGE_VERSION=$(hatch version) + echo "Creating package with version $PACKAGE_VERSION" + python -m build twine upload --repository testpypi dist/* \ No newline at end of file diff --git a/.github/workflows/selective_cache_persist.yml b/.github/workflows/selective_cache_persist.yml index e22e9e5ce..dd74de8f8 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', '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: Create cache directory diff --git a/.github/workflows/semver_test.yml b/.github/workflows/semver_test.yml new file mode 100644 index 000000000..73357af03 --- /dev/null +++ b/.github/workflows/semver_test.yml @@ -0,0 +1,25 @@ +name: Hatch SCM versioning test + +on: + workflow_dispatch: + +jobs: + versioning-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch the complete repo history (for setuptools-scm) + - uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade hatch + + # if this is a manual dispatch, upload to TestPyPI + - name: Get SCM based version from build system + run: | + hatch version \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a321f1be1..173d0c84e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,9 +12,18 @@ jobs: run-code-tests: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12'] - name: Tests on ${{ matrix.python-version }} + include: + - name-suffix: "(Minimum Versions)" + python-version: "3.8" + extra-requirements: "-c requirements/minver.txt" + - python-version: "3.8" + - python-version: "3.9" + - python-version: "3.10" + - python-version: "3.11" + - python-version: "3.12" + name: Tests on ${{ matrix.python-version }} ${{ matrix.name-suffix }} steps: - name: Setup python uses: actions/setup-python@v4 @@ -22,20 +31,21 @@ jobs: python-version: ${{ matrix.python-version }} - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache pip uses: actions/cache@v3 with: path: ~/.cache/pip - key: pip-cache-${{ hashFiles('requirements/*/*.txt') }} + key: pip-cache-${{ hashFiles('requirements/*.txt') }} restore-keys: | pip-cache - name: Install python requirements run: | - python3 -m pip install --upgrade pip setuptools wheel - python3 -m pip install -r requirements-dev.txt + python -m pip install --upgrade pip + python -m pip install --upgrade build twine + python -m pip install -r requirements/dev.txt ${{ matrix.extra-requirements }} - name: Install Fast-F1 from sources run: | @@ -57,92 +67,64 @@ jobs: run: | pytest -ra + run-lint-checks: runs-on: ubuntu-latest - name: Flake8 lint checks + name: Linting (Ruff) steps: - name: Setup python uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.12' - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache pip uses: actions/cache@v3 with: path: ~/.cache/pip - key: pip-cache-${{ hashFiles('requirements/*/*.txt') }} + key: pip-cache-${{ hashFiles('requirements/*.txt') }} restore-keys: | pip-cache - name: Install python requirements run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r requirements-dev.txt + python -m pip install --upgrade pip + python -m pip install --upgrade build setuptools twine + python -m pip install -r requirements/dev.txt - - name: Install Fast-F1 from sources + - name: Install FastF1 from sources run: | - python3 -m pip install -e . + python -m pip install -e . - name: Run tests - if: ${{ github.ref != 'refs/heads/master' }} - run: | - mkdir test_cache # make sure cache dir exists - git fetch origin --quiet - # flake8 with default config - flake8 fastf1 examples scripts - # flake8 check new shorter line length only on diff - git diff origin/master -U0 --relative | flake8 --max-line-length 79 --diff --select E501 fastf1 examples scripts - - - name: Run tests (master push) - if: ${{ github.ref == 'refs/heads/master' && env.GITHUB_EVENT_NAME == 'push'}} - env: - LAST_PUSH_SHA: ${{ github.event.before }} run: | mkdir test_cache # make sure cache dir exists git fetch origin --quiet - # flake8 with default config - flake8 fastf1 examples scripts - # flake8 check new shorter line length only on diff - echo "Flake8 line length check on diff against $LAST_PUSH_SHA" - git diff $LAST_PUSH_SHA -U0 --relative | flake8 --max-line-length 79 --diff --select E501 fastf1 examples scripts + # ruff with default config + ruff check . run-readme-render-test: name: Test readme renders on PyPi runs-on: ubuntu-latest - steps: - - name: Setup python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Checkout repo - uses: actions/checkout@v3 - - name: Cache pip - uses: actions/cache@v3 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 with: - path: ~/.cache/pip - key: pip-cache-${{ hashFiles('requirements/*/*.txt') }} - restore-keys: | - pip-cache - - - name: Install python requirements + python-version: '3.12' + - name: Install dependencies run: | - python3 -m pip install --upgrade pip setuptools wheel - python3 -m pip install -r requirements-dev.txt + python -m pip install --upgrade pip + python -m pip install --upgrade build twine - - name: Install Fast-F1 from sources + - name: Build release and check long form description run: | - python3 -m pip install -e . + python -m build + twine check dist/* - - name: Run tests - run: | - mkdir test_cache # not really need but pytest setup relies on it - pytest -rf --prj-doc run-sphinx-build-test: name: Test Docs diff --git a/.gitignore b/.gitignore index 56166fd96..9cb05c714 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ dist/ build/ fastf1.egg-info/ +# version info dynamically created by setuptools-scm +fastf1/_version.py + # temporary testrun directories fastf1/tests/testenv/ fastf1/tests/mpl-results/ @@ -25,6 +28,7 @@ fastf1/tests/mpl-baseline-new/ # documentation build directories docs/_build/ docs/examples_gallery/ +**/sg_execution_times.rst # all variations of cache directories *_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa2ea7052..f8f7dd1d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,7 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.0.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.8 hooks: - - id: flake8 - - repo: local - hooks: - - id: flake8-line-length - name: Flake8 line length - entry: python ./scripts/flake8_line_length.py - language: python + # Run the linter. + - id: ruff diff --git a/LICENSE b/LICENSE index 482f95afa..bba49afa5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Philipp Schäfer +Copyright (c) 2024 Philipp Schäfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 23d655ff8..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include requirements-dev.txt diff --git a/README.md b/README.md index 692e2c962..bf0e5a064 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ It is recommended to install FastF1 using `pip`: pip install fastf1 ``` -Note that Python 3.8 or higher is required. - Alternatively, a wheel or a source distribution can be downloaded from Pypi. You can also install using `conda`: @@ -41,9 +39,9 @@ conda install -c conda-forge fastf1 - R package that wraps FastF1: https://github.com/SCasanova/f1dataR -These packages are not directly related to the FastF1 project. Questions and -suggestions regarding these packages need to be directed at their respective -maintainers. +Third-party packages are not directly related to the FastF1 project. Questions +and suggestions regarding these packages need to be directed at their +respective maintainers. ## Documentation diff --git a/conftest.py b/conftest.py index 6c8599204..7055fad37 100644 --- a/conftest.py +++ b/conftest.py @@ -12,10 +12,6 @@ def pytest_addoption(parser): "--ergast-api", action="store_true", default=False, help="run tests which require connecting to ergast" ) - parser.addoption( - "--lint-only", action="store_true", default=False, - help="only run linter and skip all tests" - ) parser.addoption( "--prj-doc", action="store_true", default=False, help="run only tests for general project structure and documentation" @@ -65,11 +61,6 @@ def pytest_collection_modifyitems(config, items): if "ergastapi" in item.keywords: item.add_marker(skip_ergast) - # lint only: skip all - if config.getoption('--lint-only'): - items[:] = [item for item in items - if item.get_closest_marker('flake8')] - # only test documentation and project structure if config.getoption('--prj-doc'): skip_non_prj = pytest.mark.skip(reason="--prj-doc given: run only " diff --git a/docs/Makefile b/docs/Makefile index 45bad2eb2..226787c7b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -14,6 +14,9 @@ help: .PHONY: help Makefile +show: + @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/_build/html/index.html')" + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile diff --git a/docs/conf.py b/docs/conf.py index a8135af75..7b7eb9293 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,13 +10,14 @@ from datetime import datetime import os.path -import re import sys import warnings import plotly.io as pio from plotly.io._sg_scraper import plotly_sg_scraper +import fastf1 + sys.path.append(os.path.abspath('extensions')) @@ -33,16 +34,10 @@ # -- Project information ----------------------------------------------------- -# load version number from file in sources dir without importing -with open('../fastf1/version.py') as vfobj: - vstring = str(vfobj.read()) - version = re.search(r"(\d+.\d+.\d+[-\w]*)", vstring)[0] - - project = 'FastF1' # copyright = 'MIT' # author = 'Oehrly' -version = version +version = fastf1.__version__ release = version copyright = f'{datetime.now().year}, theOehrly' html_title = f"{project} {release}" diff --git a/docs/contributing/coding_guide.rst b/docs/contributing/coding_guide.rst index 00f50ae5c..b9ac1dfc3 100644 --- a/docs/contributing/coding_guide.rst +++ b/docs/contributing/coding_guide.rst @@ -58,7 +58,7 @@ Documentation * Every new feature should be documented. If it's a new module, don't forget to add a new rst file to the API docs. -* Each high-level plotting function should have a small example in +* Each high-level function should have a small example in the ``Examples`` section of the docstring. This should be as simple as possible to demonstrate the method. More complex examples should go into a dedicated example file in the :file:`examples` directory, which will be @@ -77,7 +77,7 @@ Automated tests --------------- Whenever a pull request is created or updated, various automated test tools -will run on all supported platforms and versions of Python. +will run on all supported versions of Python. Make sure that all test are passing. (All checks are listed at the bottom of the GitHub page of your pull request) diff --git a/docs/contributing/contributing.rst b/docs/contributing/contributing.rst index 469a93507..d6caedbb1 100644 --- a/docs/contributing/contributing.rst +++ b/docs/contributing/contributing.rst @@ -133,21 +133,20 @@ rules before submitting a pull request: `_. * Formatting should follow the recommendations of PEP8_, as enforced by - flake8_. The maximum line length for all changed lines is 79 characters. - You can check flake8 compliance from the command line with :: + ruff_. The maximum line length for all changed lines is 79 characters. + You can check code style compliance from the command line with :: - python -m pip install flake8 - flake8 fastf1 examples + python -m pip install ruff + ruff check . or your editor may provide integration with it. The above command will not flag lines that are too long! - Flake8 will also be run before each commit if you have the pre-commit hooks - installed (see :ref:`install_pre_commit`). Contrary to the manual invocation of flake8, this will also flag - lines which are too long! + Ruff will also be run before each commit if you have the pre-commit hooks + installed (see :ref:`install_pre_commit`). .. _PEP8: https://www.python.org/dev/peps/pep-0008/ - .. _flake8: https://flake8.pycqa.org/ + .. _ruff: https://docs.astral.sh/ruff/ * Changes (both new features and bugfixes) should have good test coverage. See :ref:`testing` for more details. @@ -169,9 +168,6 @@ rules before submitting a pull request: new contributions will move the overall code base quality in the right direction. - Most notably, all new and changed lines should adhere to the 79 character - line length limit. - .. seealso:: * :ref:`coding_guidelines` @@ -258,20 +254,19 @@ below, except in very rare circumstances as deemed necessary by the developers. This ensures that users are notified before the change will take effect and thus prevents unexpected breaking of code. -Note that FastF1 uses a rather short deprecation timeline compared to other -projects. This is necessary as FastF1 often needs to be adapted to changes in -external APIs which may come without prior warning. To be able to efficiently -keep up with these external changes, it can be necessary to make changes to -FastF1 on short notice. In general, breaking changes and deprecations should -be avoided if possible and users should be given prior warnings and as much time -as possible to adapt. +Note that FastF1 often needs to be adapted to changes in external APIs which +may come without prior warning. To be able to efficiently keep up with these +external changes, it can be necessary to make changes to FastF1 on shorter +notice than described below. In general, breaking changes and deprecations +should be avoided if possible and users should be given prior warnings and as +much time as possible to adapt. Rules ~~~~~ -- Deprecations are targeted at the next patch release (e.g. 2.3.x) -- Deprecated API is generally removed on the next point-releases (e.g. 2.x) +- Deprecations are targeted at the next point release (e.g. 3.x) +- Deprecated API is generally removed two point-releases after introduction of the deprecation. Longer deprecations can be imposed by core developers on a case-by-case basis to give more time for the transition - The old API must remain fully functional during the deprecation period @@ -284,8 +279,8 @@ Introducing 1. Announce the deprecation in the changelog :file:`docs/changelog.rst` (reference your pull request as well) -2. If possible, issue a warning when the deprecated - API is used, using the python `logging` module. +2. If possible, issue a `DeprecationWarning` when the deprecated + API is used, using the python `warnings` module. Expiring @@ -352,12 +347,14 @@ will log to a logger named ``fastf1.yourmodulename``. Which logging level to use? ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -logger + There are five levels at which you can emit messages. - `logger.critical` and `logger.error` are really only there for errors that will end the use of the library but not kill the interpreter. -- `logger.warning` are used to warn the user, see below. +- `logger.warning` is used to warn the user, for example, if an operation has + failed gracefully, if some action is likely to have unintended side-effects + or similar. - `logger.info` is for information that the user may want to know if the program behaves oddly. For instance, if a driver did not participate in a session, some data cannot be loaded for this specific driver. But FastF1 can diff --git a/docs/contributing/devenv_setup.rst b/docs/contributing/devenv_setup.rst index 9942e65f2..ad6fa1ca2 100644 --- a/docs/contributing/devenv_setup.rst +++ b/docs/contributing/devenv_setup.rst @@ -61,13 +61,13 @@ Installing additional dependencies for development To install additional dependencies for development, testing and building of the documentation, run the following command within the :file:`Fast-F1` directory:: - python -m pip install -r requirements-dev.txt + python -m pip install -r requirements/dev.txt [Optional] Installing pre-commit hooks ====================================== You can optionally install `pre-commit `_ hooks. -These will automatically check flake8 and other style issues when you run -``git commit``. The hooks are defined in the top level +These will automatically check code style issues (using the ruff linter) when +you run ``git commit``. The hooks are defined in the top level ``.pre-commit-config.yaml`` file. To install the hooks :: pip install pre-commit diff --git a/docs/contributing/documenting_fastf1.rst b/docs/contributing/documenting_fastf1.rst index f6e1b66b2..db538a45b 100644 --- a/docs/contributing/documenting_fastf1.rst +++ b/docs/contributing/documenting_fastf1.rst @@ -31,18 +31,26 @@ The documentation sources are found in the :file:`docs/` directory in the trunk. The configuration file for Sphinx is :file:`docs/conf.py`. It controls which directories Sphinx parses, how the docs are built, and how the extensions are used. To build the documentation in html format run the following command -from the project root directory: +from the :file:`docs/` directory: .. code:: sh - python setup.py build_sphinx + make html -The documentation build expects the cache directory :file:`doc_cache/` to exist. You will have to create it the first time. +The documentation build expects the cache directory :file:`doc_cache/` to exist +in the project root. +You will have to create it manually the first time you build the documentation. The generated documentation can be found in :file:`docs/_build/html` and viewed -in an internet browser by opening the html files. +in an internet browser by opening the html files. Run the following command +to open the homepage of the documentation build: + +.. code:: sh + + make show + Writing documentation --------------------- diff --git a/docs/make.bat b/docs/make.bat index 922152e96..cc3248959 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -11,6 +11,7 @@ set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help +if "%1" == "show" goto show %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( @@ -31,5 +32,8 @@ goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +:show +python -m webbrowser -t "%~dp0\_build\html\index.html" + :end popd diff --git a/fastf1/__init__.py b/fastf1/__init__.py index 67550bd5f..49f237264 100644 --- a/fastf1/__init__.py +++ b/fastf1/__init__.py @@ -82,6 +82,21 @@ For more information see :ref:`logging`. """ +try: + from . import _version +except ImportError: + _version = None + +__version__ = getattr(_version, 'version', '0.0+UNKNOWN') +__version_tuple__ = getattr(_version, 'version_tuple', (0, 0, '+UNKNOWN')) +if __version_tuple__: + # create a short version containing only the public version + __version_short__ = ".".join(str(digit) for digit in __version_tuple__ + if str(digit).isnumeric()) +else: + __version_short__ = __version__ + + from typing import Dict from fastf1.events import (get_session, # noqa: F401 @@ -94,7 +109,6 @@ from fastf1.logger import set_log_level # noqa: F401 from fastf1.req import Cache, RateLimitExceededError # noqa: F401 -from fastf1.version import __version__ # noqa: F401 _DRIVER_TEAM_MAPPING: Dict[str, Dict[str, str]] = { diff --git a/fastf1/ergast/interface.py b/fastf1/ergast/interface.py index 7790abdcc..f5c60466b 100644 --- a/fastf1/ergast/interface.py +++ b/fastf1/ergast/interface.py @@ -2,16 +2,16 @@ import json from typing import List, Literal, Optional, Union +from fastf1 import __version_short__ from fastf1.req import Cache import fastf1.ergast.structure as API -from fastf1.version import __version__ import pandas as pd BASE_URL = 'https://ergast.com/api/f1' -HEADERS = {'User-Agent': f'FastF1/{__version__}'} +HEADERS = {'User-Agent': f'FastF1/{__version_short__}'} class ErgastResponseMixin: diff --git a/fastf1/ergast/sphinx.py b/fastf1/ergast/sphinx.py index e007071cc..2e880f53d 100644 --- a/fastf1/ergast/sphinx.py +++ b/fastf1/ergast/sphinx.py @@ -4,9 +4,9 @@ import json from typing import get_type_hints +from fastf1 import __version__ from fastf1.ergast.interface import ErgastResultFrame import fastf1.ergast.structure -from fastf1.version import __version__ class ApiMappingDirective(Directive): diff --git a/fastf1/events.py b/fastf1/events.py index 6c214d980..473bd633a 100644 --- a/fastf1/events.py +++ b/fastf1/events.py @@ -365,7 +365,7 @@ def get_event( force_ergast=force_ergast, backend=backend) - if type(gp) is str: + if isinstance(gp, str): event = schedule.get_event_by_name(gp, strict_search=strict_search) else: event = schedule.get_event_by_round(gp) diff --git a/fastf1/mvapi/api.py b/fastf1/mvapi/api.py index 21081fe69..c07864cf4 100644 --- a/fastf1/mvapi/api.py +++ b/fastf1/mvapi/api.py @@ -2,14 +2,13 @@ from typing import Optional +from fastf1 import __version_short__ from fastf1.mvapi.internals import _logger from fastf1.req import Cache -from fastf1.version import __version__ - PROTO = "https" HOST = "api.multiviewer.app" -HEADERS = {'User-Agent': f'FastF1/{__version__}'} +HEADERS = {'User-Agent': f'FastF1/{__version_short__}'} def _make_url(path: str): diff --git a/fastf1/version.py b/fastf1/version.py deleted file mode 100644 index f71b21a5d..000000000 --- a/fastf1/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '3.1.2' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c4e87b304 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,89 @@ +[project] +dynamic = ["version"] + +name = "fastf1" +authors = [ + {email = "oehrly@mailbox.org"}, + {name = "Philipp Schaefer"} +] +description = "Python package for accessing and analyzing Formula 1 results, schedules, timing data and telemetry." +readme = "README.md" + +license = { file = "LICENSE" } + +# minimum python version additionally needs to be changed in the test matrix +requires-python = ">=3.8" +# minimum package versions additionally need to be changed in requirement/minver.txt +dependencies = [ + "matplotlib>=3.5.1,<4.0.0", + "numpy>=1.21.5,<2.0.0", + "pandas>=1.3.5,<2.1.0", + "python-dateutil", + "requests>=2.28.1", + "requests-cache>=0.8.0", + "scipy>=1.7.3,<2.0.0", + "thefuzz", + "timple>=0.1.6", + "websockets>=10.3", +] + +[project.urls] +"Source Code" = "https://github.com/theOehrly/Fast-F1" +"Documentation" = "https://docs.fastf1.dev" + +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.targets.wheel] +ignore-vcs = true +include = [ + "fastf1/**", +] +exclude = [ + "fastf1/tests/**", + "fastf1/testing/**", +] + +[tool.hatch.build.targets.sdist] +ignore-vcs = true +include = [ + "fastf1/**", + "requirements/**", + "examples/**" +] +exclude = [ + "fastf1/tests/**", + "fastf1/testing/**", +] + +[tool.hatch.build.hooks.vcs] +version-file = "fastf1/_version.py" + +[tool.hatch.version.raw-options] +# configures setuptools_scm within the hatchling build backend +version_scheme = "release-branch-semver" +local_scheme = "no-local-version" # needed for PyPI support for dev releases; hatch-vcs ignores setuptools_scm overrides through env vars +fallback_version = "0.0+UNKNOWN" + +[tool.ruff] +line-length = 79 +select = [ +# "D", TODO: select and fix docstrings + "E", + "F", + "W", +] +exclude = [ + "scripts", + "fastf1/tests", + "fastf1/testing", + "fastf1/signalr_aio", + "fastf1/legacy.py", +] + +[tool.ruff.per-file-ignores] +"fastf1/_api.py" = ["E501"] diff --git a/pytest.ini b/pytest.ini index 276b0c917..aedaf6184 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,9 +4,7 @@ minversion = 6.0 testpaths = fastf1 docs - # the following are only used for 'pytest --flake8' examples - scripts norecursedirs = _build diff --git a/requirements-dev.txt b/requirements/dev.txt similarity index 74% rename from requirements-dev.txt rename to requirements/dev.txt index 0cd0eeb82..e0f12108b 100644 --- a/requirements-dev.txt +++ b/requirements/dev.txt @@ -1,19 +1,15 @@ +autodocsumm>=0.2.10 # support for sphinx v6 +furo +kaleido +plotly +pre-commit pytest>=6.0.0 # equals pytest min version requirement pytest-mpl==0.14.0 # logging issues in newer versions requests-mock +ruff +seaborn<0.13.0 sphinx>6.0 -furo -sphinx-gallery>=0.10.0 -autodocsumm>=0.2.10 # support for sphinx v6 sphinx-autodoc-typehints +sphinx-gallery>=0.10.0 wheel -flake8<5.0.0 -readme-renderer[md] -xdoctest -pre-commit -requests>=2.28.1 -websockets>=10.3 -seaborn<0.13.0 -plotly -seaborn -kaleido \ No newline at end of file +xdoctest \ No newline at end of file diff --git a/requirements/minver.txt b/requirements/minver.txt new file mode 100644 index 000000000..88389f6f1 --- /dev/null +++ b/requirements/minver.txt @@ -0,0 +1,8 @@ +matplotlib==3.5.1 +numpy==1.21.5 +pandas==1.3.5 +requests==2.28.1 +requests-cache==0.8.0 +scipy==1.7.3 +timple==0.1.6 +websockets==10.3 \ No newline at end of file diff --git a/scripts/flake8_line_length.py b/scripts/flake8_line_length.py deleted file mode 100644 index c8d19b4c7..000000000 --- a/scripts/flake8_line_length.py +++ /dev/null @@ -1,18 +0,0 @@ -"""This script will run flake8 but only check for then new line length limit of -79 characters. The checks are limited to the currently staged changes.""" -import os -import subprocess -import sys - -print(os.getcwd()) - - -p_diff = subprocess.Popen(["git", "diff", "--cached", "-U0", "--relative"], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - -p_flake8 = subprocess.Popen(["flake8", "--max-line-length", "79", - "--select", "E501", "--diff", - "fastf1 examples scripts"], - stdin=p_diff.stdout) -p_flake8.wait() -sys.exit(p_flake8.returncode) diff --git a/scripts/visualization.py b/scripts/visualization.py deleted file mode 100644 index ecfdf5ff3..000000000 --- a/scripts/visualization.py +++ /dev/null @@ -1,234 +0,0 @@ -import matplotlib.pyplot as plt -from mpl_toolkits.axes_grid1 import make_axes_locatable -import numpy as np -import pandas as pd -import os -import fastf1 as ff1 - - -def show_deviation_minima_on_track(res, track, sector_number): - mad_x_stats = res['mad_x{}'.format(sector_number)] - mad_y_stats = res['mad_y{}'.format(sector_number)] - mean_x_stats = res['mean_x{}'.format(sector_number)] - mean_y_stats = res['mean_y{}'.format(sector_number)] - - mean_x_stats = np.array(mean_x_stats) - mean_y_stats = np.array(mean_y_stats) - mad_x_stats = np.array(mad_x_stats) - mad_y_stats = np.array(mad_y_stats) - - x_minima = np.r_[True, mad_x_stats[1:] < mad_x_stats[:-1]] & np.r_[mad_x_stats[:-1] < mad_x_stats[1:], True] - y_minima = np.r_[True, mad_y_stats[1:] < mad_y_stats[:-1]] & np.r_[mad_y_stats[:-1] < mad_y_stats[1:], True] - - ax_main = plt.subplot(label='Track Map') - plt.plot(track.sorted_x, track.sorted_y) - ax_main.set_aspect('equal') - ax_main.set_xlabel('X') - ax_main.set_ylabel('Y') - ax_main.yaxis.set_tick_params(labelleft=False, labelright=True) - ax_main.yaxis.set_label_position("right") - - # x deviation minima - for x_min in mean_x_stats[x_minima]: - ax_main.axvline(x_min, color='r') - - # y deviation minima - for y_min in mean_y_stats[y_minima]: - ax_main.axhline(y_min, color='r') - - divider = make_axes_locatable(ax_main) - ax_mad_x = divider.append_axes("top", 1.2, pad=0.1, sharex=ax_main) - ax_mad_y = divider.append_axes("left", 1.2, pad=0.1, sharey=ax_main) - - ax_mad_x.plot(mean_x_stats, mad_x_stats) - ax_mad_x.grid(which='both') - ax_mad_x.set_ylabel('Y MAD {}'.format(sector_number)) - ax_mad_x.xaxis.set_tick_params(labelbottom=False) - - ax_mad_y.plot(mad_y_stats, mean_y_stats) - ax_mad_y.grid(which='both') - ax_mad_y.invert_xaxis() - ax_mad_y.set_xlabel('X MAD {}'.format(sector_number)) - ax_mad_y.yaxis.set_tick_params(labelleft=False) - - -def plot_lap_time_integrity(laps_data, suffix=''): - drivers_series = laps_data.DriverResult - drivers = list(drivers_series.drop_duplicates()) - - deltas = list() - ref = list() # scatter plots need an x and y value therefore ref is created by counting up - n = 0 - - if 'Date' in laps_data.columns: - for driver in drivers: - i_max = len(laps_data[laps_data.DriverResult == driver]) - for i in range(1, i_max): - delta = (laps_data.iloc[i].Date - laps_data.iloc[i].LapTime - laps_data.iloc[i-1].Date).total_seconds() - deltas.append(delta) - ref.append(n) - n += 1 - else: - for driver in drivers: - i_max = len(laps_data[laps_data.DriverResult == driver]) - for i in range(1, i_max): - delta = (laps_data.iloc[i].Time - laps_data.iloc[i].LapTime - laps_data.iloc[i-1].Time).total_seconds() - deltas.append(delta) - ref.append(n) - n += 1 - - fig1 = plt.figure() - fig1.suptitle("Lap Time Scatter {}".format(suffix)) - plt.scatter(ref, deltas) - - fig2 = plt.figure() - fig2.suptitle("Lap Time Histogram {}".format(suffix)) - plt.hist(deltas, bins=50) - - -def plot_lap_position_integrity(laps_data, pos_data, suffix=''): - vals = dict() - - for _, lap in laps_data.pick_wo_box().pick_track_status('1').iterlaps(require=['LapStartDate', 'DriverNumber']): - if not lap['IsAccurate']: - continue - if lap['LapStartDate'] not in pos_data[lap['DriverNumber']]['Date']: - tmp_add = pd.DataFrame({}, index=[lap['LapStartDate'], ]) - tmp_df = pos_data[lap['DriverNumber']].set_index('Date').append(tmp_add).sort_index() - tmp_df.loc[:, ('X', 'Y')] = tmp_df.loc[:, ('X', 'Y')].interpolate(method='quadratic') - - row = tmp_df[tmp_df.index == lap['LapStartDate']].iloc[0] - - else: - row = pos_data[lap['DriverNumber']][pos_data[lap['DriverNumber']]['Date'] == lap['LapStartDate']].iloc[0] - - if lap['DriverNumber'] not in vals.keys(): - vals[lap['DriverNumber']] = {'x': list(), 'y': list(), 't': list()} - - vals[lap['DriverNumber']]['x'].append(row['X']) - vals[lap['DriverNumber']]['y'].append(row['Y']) - vals[lap['DriverNumber']]['t'].append(lap['Time'].total_seconds()) - - x_vals, y_vals = list(), list() - for drv in vals.keys(): - x_vals.extend(vals[drv]['x']) - y_vals.extend(vals[drv]['y']) - - y_mean_tot = np.mean(y_vals) - - fig0y = plt.figure() - fig0y.suptitle("Time Scatter {}".format(suffix)) - for drv in vals.keys(): - plt.plot(vals[drv]['t'], vals[drv]['y'], label=drv) - plt.legend() - - fig0cy = plt.figure() - fig0cy.suptitle("Time Scatter Mean correction {}".format(suffix)) - for drv in vals.keys(): - plt.plot(vals[drv]['t'], np.array(vals[drv]['y']) + (y_mean_tot - np.mean(vals[drv]['y'])), label=drv) - plt.legend() - - fig1 = plt.figure() - fig1.suptitle("Position Scatter {}".format(suffix)) - for drv in vals.keys(): - plt.scatter(vals[drv]['x'], vals[drv]['y'], label=drv) - plt.axis('equal') - plt.legend() - - fig2 = plt.figure() - fig2 .suptitle("Position X Histogram {}".format(suffix)) - plt.hist(x_vals, bins=30) - - fig3 = plt.figure() - fig3.suptitle("Position Y Histogram {}".format(suffix)) - plt.hist(y_vals, bins=30) - - -def all_sectors_result_plots(result, track, suffix, workdir=None): - r = result - - # mean absolute deviation x - plt.figure(figsize=(15, 8)) - plt.suptitle('MAD X | {}'.format(suffix)) - plt.plot(r['mad_x1'], label="mad_x1") - plt.plot(r['mad_x2'], label="mad_x2") - plt.plot(r['mad_x3'], label="mad_x3") - plt.legend() - plt.grid(which='both') - if workdir: - plt.savefig(os.path.join(workdir, 'mad x {}.png'.format(suffix)), dpi=300) - - # mean absolute deviation y - plt.figure(figsize=(15, 8)) - plt.suptitle('MAD Y | {}'.format(suffix)) - plt.plot(r['mad_y1'], label="mad_y1") - plt.plot(r['mad_y2'], label="mad_y2") - plt.plot(r['mad_y3'], label="mad_y3") - plt.legend() - plt.grid(which='both') - if workdir: - plt.savefig(os.path.join(workdir, 'mad y {}.png'.format(suffix)), dpi=300) - - # track + mad plot one for each sector - plt.figure(figsize=(15, 8)) - plt.suptitle('Track + MAD 1 | {}'.format(suffix)) - show_deviation_minima_on_track(r, track, 1) - if workdir: - plt.savefig(os.path.join(workdir, 'track plus mad 1 {}.png'.format(suffix)), dpi=300) - - plt.figure(figsize=(15, 8)) - plt.suptitle('Track + MAD 2 | {}'.format(suffix)) - show_deviation_minima_on_track(r, track, 2) - if workdir: - plt.savefig(os.path.join(workdir, 'track plus mad 2 {}.png'.format(suffix)), dpi=300) - - plt.figure(figsize=(15, 8)) - plt.suptitle('Track + MAD 3 | {}'.format(suffix)) - show_deviation_minima_on_track(r, track, 3) - if workdir: - plt.savefig(os.path.join(workdir, 'track plus mad 3 {}.png'.format(suffix)), dpi=300) - - # delta between test value and mean result - dx = list() - for test, tres in zip(r['tx'], r['mean_x1']): - dx.append(test-tres) - - dy = list() - for test, tres in zip(r['ty'], r['mean_y1']): - dy.append(test-tres) - - plt.figure(figsize=(15, 8)) - plt.suptitle('Difference In - Out Coordinate | {}'.format(suffix)) - plt.plot(dx, label='dx') - plt.plot(dy, label='dy') - plt.legend() - plt.grid(which='both') - if workdir: - plt.savefig(os.path.join(workdir, 'coord diff {}.png'.format(suffix)), dpi=300) - - plt.show() - - -def delta_time_validation(ref_lap, comp_lap): - from fastf1 import plotting - plotting.setup_mpl() - plt.figure() - - dt, ref_tel, compare_tel = ff1.utils.delta_time(ref_lap, comp_lap) - - sec12 = ref_tel['Distance'].iloc[np.argmin(abs(ref_tel['SessionTime'] - ref_lap['Sector1SessionTime']))] - sec23 = ref_tel['Distance'].iloc[np.argmin(abs(ref_tel['SessionTime'] - ref_lap['Sector2SessionTime']))] - plt.vlines(sec12, min(dt), max(dt), colors='yellow') - plt.vlines(sec23, min(dt), max(dt), colors='yellow') - plt.vlines(1.0, min(dt), max(dt), colors='yellow') - - plt.plot(ref_tel['Distance'], dt, label='Gap') - - dts1 = comp_lap['Sector1Time'] - ref_lap['Sector1Time'] - dts2 = (comp_lap['Sector1Time'] + comp_lap['Sector2Time']) - (ref_lap['Sector1Time'] + ref_lap['Sector2Time']) - dtsL = comp_lap['LapTime'] - ref_lap['LapTime'] - plt.hlines(dts1.total_seconds(), 0, max(ref_tel['Distance']), label='Sec1', colors='blue') - plt.hlines(dts2.total_seconds(), 0, max(ref_tel['Distance']), label='Sec2', colors='pink') - plt.hlines(dtsL.total_seconds(), 0, max(ref_tel['Distance']), label='Sec3', colors='grey') - - plt.legend() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 47ff695a4..000000000 --- a/setup.cfg +++ /dev/null @@ -1,49 +0,0 @@ -[metadata] -name = fastf1 -license = MIT -license_files = LICENSE -author = Oehrly -author_email = oehrly@mailbox.org -home_page = https://github.com/theOehrly/Fast-F1 -description = Wrapper library for F1 data and telemetry API with additional data processing capabilities. -long_description = file: README.md -long_description_content_type = text/markdown - - -[options] -zip_safe = False -packages = - fastf1 - fastf1.ergast - fastf1.internals - fastf1.livetiming - fastf1.mvapi - fastf1.signalr_aio - fastf1.signalr_aio.events - fastf1.signalr_aio.hubs - fastf1.signalr_aio.transports - -python_requires = >= 3.8 -install_requires = - requests-cache>=0.8.0 - pandas>=1.2.4,<2.1.0 - numpy>=1.20.3,<2.0.0 - scipy>=1.6.3,<2.0.0 - thefuzz - matplotlib>=3.4.2,<4.0.0 - python-dateutil - timple>=0.1.6 - requests>=2.28.0 - websockets>=8.1 - - -[build_sphinx] -project = Fast F1 -source-dir = ./docs -build-dir = ./docs/_build - -[flake8] -max-line-length = 120 -extend-ignore = W503 -extend-select = W504 -exclude = fastf1/signalr_aio diff --git a/setup.py b/setup.py deleted file mode 100644 index a0a0edeff..000000000 --- a/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -from setuptools import setup - - -if __name__ == '__main__': - import sys - import re - if sys.version_info < (3, 8): - sys.exit('Sorry, Python < 3.8 is not supported,' - + ' please update to install.') - else: - # load version number from file in sources dir without importing - with open('fastf1/version.py') as vfobj: - vstring = str(vfobj.read()) - version = re.search(r"(\d+.\d+.\d+[-\w]*)", vstring)[0] - setup(version=version)