diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9c48df5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,153 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read # For checkout and comparing commits + pull-requests: write # For creating/updating PR comments + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install Poetry + uses: abatilo/actions-poetry@v4 + with: + poetry-version: 2.2.1 + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache/restore@v5 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + run: poetry install + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + + - name: Cache venv + id: cache-poetry-dependencies + uses: actions/cache/save@v5 + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + lint: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install Poetry + uses: abatilo/actions-poetry@v4 + with: + poetry-version: 2.2.1 + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache/restore@v5 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + run: poetry install + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + + - name: Run linter + run: | + source ./.venv/bin/activate + poetry run ruff check --config=pyproject.toml + + format: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install Poetry + uses: abatilo/actions-poetry@v4 + with: + poetry-version: 2.2.1 + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache/restore@v5 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + run: poetry install + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + + - name: Run linter + run: | + source ./.venv/bin/activate + poetry run ruff format --check --config=pyproject.toml + + test: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install Poetry + uses: abatilo/actions-poetry@v4 + with: + poetry-version: 2.2.1 + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache/restore@v5 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + run: poetry install + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + + - name: Run tests with coverage + run: | + source ./.venv/bin/activate + set -o pipefail + poetry run pytest ./tests --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=f1p | tee pytest-coverage.txt + + - name: Pytest Coverage Comment + uses: MishaKav/pytest-coverage-comment@v1.2.0 + with: + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml + github-token: ${{ secrets.GITHUB_TOKEN }} + create-new-comment: true diff --git a/.gitignore b/.gitignore index cb2d456..4d00afb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ __pycache__/ *.py[codz] *$py.class .idea -.fastf1-cache \ No newline at end of file +.fastf1-cache +.coverage +coverage.xml \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 848bb39..eceaacf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -438,104 +438,104 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "coverage" -version = "7.13.0" +version = "7.13.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, - {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, - {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, - {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, - {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, - {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, - {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, - {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, - {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, - {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, - {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, - {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, - {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, - {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, - {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, - {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, - {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, - {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, - {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, - {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, - {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, - {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, - {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, - {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, - {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, - {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, - {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, - {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, - {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, - {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, - {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, - {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, - {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, - {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, - {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, - {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, - {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, - {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, - {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, - {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, - {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, - {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, - {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, - {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, + {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, + {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, + {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, + {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, + {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, + {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, + {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, + {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, + {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, + {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, + {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, + {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, + {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, + {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, + {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, + {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, + {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, + {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, + {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, + {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, + {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, + {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, ] [package.extras] @@ -2171,5 +2171,5 @@ files = [ [metadata] lock-version = "2.1" -python-versions = ">=3.13,<4.0" -content-hash = "9ff5fa3bd6eaecd809a01de5dfe7ebbd7abd1c72b4a67819c2b5fd51765a938b" +python-versions = ">=3.14,<4.0" +content-hash = "f1a584d4bf60b74aabbe2d24b1829ebe80f5c9f6d74235477fa5e560a18f469d" diff --git a/pyproject.toml b/pyproject.toml index dfbef04..c81856e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "F1 Player" -version = "0.0.1" +version = "0.0.4" description = "A tool for watching F1 races from a data perspective." authors = [ "Mitko Toshev " @@ -9,15 +9,15 @@ readme = "README.md" packages = [{include = "f1p", from = "src"}] [tool.poetry.dependencies] -python = ">=3.13,<4.0" +python = ">=3.14,<4.0" fastf1 = "^3.7.0" panda3d = "^1.10.15" pandas-stubs = "^2.3.3.251219" - [tool.poetry.group.dev.dependencies] ruff = "^0.12.8" pytest = "8.4.1" +coverage = "^7.13.1" pytest-mock = "3.14.1" pytest-cov = "6.2.1" pytest-asyncio = "1.1.0" @@ -30,13 +30,24 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -f1p = "f1p.main:app" +f1p = "f1p:app" + +[tool.coverage.run] +branch = true +relative_files = true +omit = [ + "tests/*", + "src/procedural3d", +] [tool.ruff] -target-version = "py313" +target-version = "py314" line-length = 120 src = ["src", "tests"] preview = true +exclude = [ + "src/procedural3d" +] [tool.ruff.lint] select = ["E", "F", "I", "W", "ARG", "A", "COM", "C4", "PIE", "T20", "PT"] diff --git a/src/f1p/main.py b/src/f1p/main.py index b6deffe..9a220f5 100644 --- a/src/f1p/main.py +++ b/src/f1p/main.py @@ -66,7 +66,7 @@ def register_ui_components(self) -> Self: 30, self.symbols_font, self.text_font, - self.data_extractor + self.data_extractor, ) circuit_map = Map(self.render, self.data_extractor) @@ -76,7 +76,7 @@ def register_ui_components(self) -> Self: self.symbols_font, self.text_font, circuit_map, - self.data_extractor + self.data_extractor, ) self.ui_components = [ @@ -90,9 +90,4 @@ def register_ui_components(self) -> Self: app = F1PlayerApp() app.disableMouse() # disable camera controls -( - app.configure_window() - .draw_menu() - .register_ui_components() - .run() -) +(app.configure_window().draw_menu().register_ui_components().run()) diff --git a/src/f1p/services/data_extractor/__init__.py b/src/f1p/services/data_extractor/__init__.py index 1ed1a96..3471b5a 100644 --- a/src/f1p/services/data_extractor/__init__.py +++ b/src/f1p/services/data_extractor/__init__.py @@ -5,20 +5,20 @@ import fastf1 import pandas as pd from direct.showbase.MessengerGlobal import messenger -from fastf1.core import Session, Lap, Laps, Telemetry -from fastf1.events import EventSchedule, Event +from fastf1.core import Lap, Laps, Session, Telemetry +from fastf1.events import Event, EventSchedule from fastf1.mvapi import CircuitInfo -from panda3d.core import deg2Rad, LVecBase4f -from pandas import DataFrame, Timedelta, Series +from panda3d.core import LVecBase4f, deg2Rad +from pandas import DataFrame, Series, Timedelta -from f1p.utils.geometry import find_center, resize_pos_data, center_pos_data +from f1p.utils.geometry import center_pos_data, find_center, resize_pos_data class DataExtractorService: year: int event_name: str session_id: str - cache_path: Path = Path(__file__).parent.parent.parent.parent.parent / '.fastf1-cache' + cache_path: Path = Path(__file__).parent.parent.parent.parent.parent / ".fastf1-cache" def __init__(self): self._event_schedule: EventSchedule | None = None @@ -151,8 +151,8 @@ def track_status_colors(self) -> DataFrame: LVecBase4f(1, 1, 1, 0.8), LVecBase4f(0, 0, 0, 0.8), LVecBase4f(0, 0, 0, 0.8), - ] - } + ], + }, ) return self._track_status_colors @@ -218,7 +218,7 @@ def process_track_statuses(self, width: int) -> None: pixel_per_tick = width / self.session_ticks - df.loc[:,"Pixel"] = df.loc[:,"SessionTimeTick"] * pixel_per_tick + df.loc[:, "Pixel"] = df.loc[:, "SessionTimeTick"] * pixel_per_tick ts_df = self.track_status.copy() ts_df = ts_df[ts_df["Time"] >= self.session_start_time] @@ -228,16 +228,22 @@ def process_track_statuses(self, width: int) -> None: for record in ts_df.itertuples(): ts_df.loc[ts_df["Time"] == record.Time, "SessionTimeTick"] = df.loc[ - df["SessionTime"] <= record.Time, "SessionTimeTick"].max() + df["SessionTime"] <= record.Time, + "SessionTimeTick", + ].max() ts_df.loc[ts_df["Time"] == record.Time, "SessionTimeTickEnd"] = df.loc[ - df["SessionTime"] <= record.EndTime, "SessionTimeTick"].max() + df["SessionTime"] <= record.EndTime, + "SessionTimeTick", + ].max() ts_df["SessionTimeTick"] = ts_df["SessionTimeTick"].astype("int64") ts_df["SessionTimeTickEnd"] = ts_df["SessionTimeTickEnd"].astype("int64") ts_df = ts_df.merge(df, on="SessionTimeTick", how="left") ts_df = ts_df.rename(columns={"Pixel": "PixelStart"}).drop(columns="SessionTime") - ts_df = ts_df.merge(df, left_on="SessionTimeTickEnd", right_on="SessionTimeTick", how="left").rename(columns={"SessionTimeTick_x": "SessionTimeTick"}) + ts_df = ts_df.merge(df, left_on="SessionTimeTickEnd", right_on="SessionTimeTick", how="left").rename( + columns={"SessionTimeTick_x": "SessionTimeTick"}, + ) ts_df = ts_df.rename(columns={"Pixel": "PixelEnd"}).drop(columns=["SessionTime", "SessionTimeTick_y"]) ts_df = ts_df.drop(columns=["Time", "EndTime"]).reset_index() @@ -286,7 +292,8 @@ def combine_position_data(self) -> Self: def remove_records_before_session_start_time(self) -> Self: self.processed_pos_data = self.processed_pos_data[ - self.processed_pos_data["SessionTime"] >= self.session_start_time] + self.processed_pos_data["SessionTime"] >= self.session_start_time + ] return self @@ -318,24 +325,31 @@ def process_laps(self) -> Self: laps = self.laps.copy() laps.loc[laps["Sector1SessionTime"].isna(), "Sector1SessionTime"] = ( - laps.loc[laps["Sector1SessionTime"].isna(), "LapStartTime"] + laps.loc[laps["Sector1SessionTime"].isna(), "Sector1Time"] + laps.loc[laps["Sector1SessionTime"].isna(), "LapStartTime"] + + laps.loc[laps["Sector1SessionTime"].isna(), "Sector1Time"] ) laps.loc[laps["Sector2SessionTime"].isna(), "Sector2SessionTime"] = ( - laps.loc[laps["Sector2SessionTime"].isna(), "Sector1SessionTime"] + laps.loc[ - laps["Sector2SessionTime"].isna(), "Sector2Time"] + laps.loc[laps["Sector2SessionTime"].isna(), "Sector1SessionTime"] + + laps.loc[laps["Sector2SessionTime"].isna(), "Sector2Time"] ) laps.loc[laps["Sector3SessionTime"].isna(), "Sector3SessionTime"] = ( - laps.loc[laps["Sector3SessionTime"].isna(), "Sector2SessionTime"] + laps.loc[ - laps["Sector3SessionTime"].isna(), "Sector3Time"] + laps.loc[laps["Sector3SessionTime"].isna(), "Sector2SessionTime"] + + laps.loc[laps["Sector3SessionTime"].isna(), "Sector3Time"] ) lap_start_time_in_milliseconds = laps["LapStartTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 laps["LapStartTimeMilliseconds"] = lap_start_time_in_milliseconds.astype("int64") - sector1_session_time_in_milliseconds = laps["Sector1SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + sector1_session_time_in_milliseconds = ( + laps["Sector1SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + ) laps["Sector1SessionTimeMilliseconds"] = sector1_session_time_in_milliseconds.astype("int64") - sector2_session_time_in_milliseconds = laps["Sector2SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + sector2_session_time_in_milliseconds = ( + laps["Sector2SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + ) laps["Sector2SessionTimeMilliseconds"] = sector2_session_time_in_milliseconds.astype("int64") - sector3_session_time_in_milliseconds = laps["Sector3SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + sector3_session_time_in_milliseconds = ( + laps["Sector3SessionTime"].fillna(Timedelta(milliseconds=0)).dt.total_seconds() * 1e3 + ) laps["Sector3SessionTimeMilliseconds"] = sector3_session_time_in_milliseconds.astype("int64") lap_time_in_milliseconds = laps["LapTime"].fillna(Timedelta(milliseconds=1)).dt.total_seconds() * 1e3 @@ -348,9 +362,21 @@ def process_laps(self) -> Self: pit_out_time_in_milliseconds = laps.loc[laps["PitOutTime"].notna(), "PitOutTime"].dt.total_seconds() * 1e3 laps.loc[laps["PitOutTime"].notna(), "PitOutTimeMilliseconds"] = pit_out_time_in_milliseconds.astype("int64") - laps["S1DiffToCarAhead"] = laps.sort_values(by=["Sector1SessionTimeMilliseconds"], ascending=[True]).groupby("LapNumber")["Sector1SessionTimeMilliseconds"].diff() - laps["S2DiffToCarAhead"] = laps.sort_values(by=["Sector2SessionTimeMilliseconds"], ascending=[True]).groupby("LapNumber")["Sector2SessionTimeMilliseconds"].diff() - laps["S3DiffToCarAhead"] = laps.sort_values(by=["Sector3SessionTimeMilliseconds"], ascending=[True]).groupby("LapNumber")["Sector3SessionTimeMilliseconds"].diff() + laps["S1DiffToCarAhead"] = ( + laps.sort_values(by=["Sector1SessionTimeMilliseconds"], ascending=[True]) + .groupby("LapNumber")["Sector1SessionTimeMilliseconds"] + .diff() + ) + laps["S2DiffToCarAhead"] = ( + laps.sort_values(by=["Sector2SessionTimeMilliseconds"], ascending=[True]) + .groupby("LapNumber")["Sector2SessionTimeMilliseconds"] + .diff() + ) + laps["S3DiffToCarAhead"] = ( + laps.sort_values(by=["Sector3SessionTimeMilliseconds"], ascending=[True]) + .groupby("LapNumber")["Sector3SessionTimeMilliseconds"] + .diff() + ) laps["LastLapTimeMilliseconds"] = laps.groupby("DriverNumber")["LapTimeMilliseconds"].shift(1) laps["FastestLapTimeMillisecondsSoFar"] = laps.groupby("DriverNumber")["LastLapTimeMilliseconds"].cummin() @@ -367,32 +393,43 @@ def merge_pos_and_laps(self) -> Self: for lap in laps_df.itertuples(): pos_data_df.loc[ - (pos_data_df["DriverNumber"] == lap.DriverNumber) & - (pos_data_df["SessionTimeMilliseconds"] >= lap.LapStartTimeMilliseconds) & - (pos_data_df["SessionTimeMilliseconds"] < lap.LapEndTimeMilliseconds), - "LapNumber" + (pos_data_df["DriverNumber"] == lap.DriverNumber) + & (pos_data_df["SessionTimeMilliseconds"] >= lap.LapStartTimeMilliseconds) + & (pos_data_df["SessionTimeMilliseconds"] < lap.LapEndTimeMilliseconds), + "LapNumber", ] = lap.LapNumber - combined_df = ( - pos_data_df.merge(laps_df, on=["DriverNumber", "LapNumber"], how="left") - .rename(columns={"Time_x": "Time", "Time_y": "Time_Lap"}) + combined_df = pos_data_df.merge(laps_df, on=["DriverNumber", "LapNumber"], how="left").rename( + columns={"Time_x": "Time", "Time_y": "Time_Lap"}, ) combined_df["LapNumber"] = combined_df.groupby("DriverNumber")["LapNumber"].ffill() - combined_df["LapStartTimeMilliseconds"] = combined_df.groupby("DriverNumber")["LapStartTimeMilliseconds"].ffill() + combined_df["LapStartTimeMilliseconds"] = combined_df.groupby("DriverNumber")[ + "LapStartTimeMilliseconds" + ].ffill() combined_df["LapEndTimeMilliseconds"] = combined_df.groupby("DriverNumber")["LapEndTimeMilliseconds"].ffill() combined_df.loc[ - (combined_df["LapNumber"] == self.total_laps) & - (combined_df["SessionTimeMilliseconds"] > combined_df["LapEndTimeMilliseconds"]), - "LapNumber" + (combined_df["LapNumber"] == self.total_laps) + & (combined_df["SessionTimeMilliseconds"] > combined_df["LapEndTimeMilliseconds"]), + "LapNumber", ] = self.total_laps + 1 - combined_df["ElapsedTimeSinceStartOfLapMilliseconds"] = combined_df["SessionTimeMilliseconds"] - combined_df["LapStartTimeMilliseconds"] - combined_df["LapPercentageCompletion"] = combined_df["ElapsedTimeSinceStartOfLapMilliseconds"] / combined_df["LapTimeMilliseconds"] + combined_df["ElapsedTimeSinceStartOfLapMilliseconds"] = ( + combined_df["SessionTimeMilliseconds"] - combined_df["LapStartTimeMilliseconds"] + ) + combined_df["LapPercentageCompletion"] = ( + combined_df["ElapsedTimeSinceStartOfLapMilliseconds"] / combined_df["LapTimeMilliseconds"] + ) combined_df["LapPercentageCompletion"] = combined_df["LapPercentageCompletion"].fillna(0) combined_df["LapsCompletion"] = (combined_df["LapNumber"] - 1) + combined_df["LapPercentageCompletion"] - combined_df["PositionIndex"] = combined_df.sort_values(by=["SessionTimeTick", "LapsCompletion"], ascending=[True, False]).groupby("SessionTimeTick").cumcount().add(1) - 1 + combined_df["PositionIndex"] = ( + combined_df.sort_values(by=["SessionTimeTick", "LapsCompletion"], ascending=[True, False]) + .groupby("SessionTimeTick") + .cumcount() + .add(1) + - 1 + ) combined_df.loc[combined_df["Position"].notna(), "IsDNF"] = False combined_df.loc[combined_df["Position"].isna(), "IsDNF"] = True @@ -404,31 +441,41 @@ def merge_pos_and_laps(self) -> Self: combined_df.loc[combined_df["SessionTimeMilliseconds"] >= end_of_race, "PositionIndex"] = pd.NA combined_df["PositionIndex"] = combined_df.groupby("DriverNumber")["PositionIndex"].ffill().astype("int64") - combined_df["FastestLapTimeMilliseconds"] = combined_df.sort_values(by=["SessionTimeTick", "LapsCompletion"], ascending=[True, False])["FastestLapTimeMillisecondsSoFar"].cummin() - combined_df.loc[combined_df["FastestLapTimeMillisecondsSoFar"] == combined_df["FastestLapTimeMilliseconds"], "HasFastestLap"] = True + combined_df["FastestLapTimeMilliseconds"] = combined_df.sort_values( + by=["SessionTimeTick", "LapsCompletion"], + ascending=[True, False], + )["FastestLapTimeMillisecondsSoFar"].cummin() + combined_df.loc[ + combined_df["FastestLapTimeMillisecondsSoFar"] == combined_df["FastestLapTimeMilliseconds"], + "HasFastestLap", + ] = True combined_df.loc[combined_df["HasFastestLap"].isna(), "HasFastestLap"] = False combined_df["HasFastestLap"] = combined_df.groupby("DriverNumber")["HasFastestLap"].ffill() combined_df.loc[ (combined_df["SessionTimeMilliseconds"] >= combined_df["Sector1SessionTimeMilliseconds"]), - "DiffToCarInFront" + "DiffToCarInFront", ] = combined_df["S1DiffToCarAhead"] combined_df.loc[ (combined_df["SessionTimeMilliseconds"] >= combined_df["Sector2SessionTimeMilliseconds"]), - "DiffToCarInFront" + "DiffToCarInFront", ] = combined_df["S2DiffToCarAhead"] combined_df.loc[ (combined_df["SessionTimeMilliseconds"] >= combined_df["Sector3SessionTimeMilliseconds"]), - "DiffToCarInFront" + "DiffToCarInFront", ] = combined_df["S3DiffToCarAhead"] combined_df.loc[ (combined_df["PositionIndex"] == 0), - "DiffToCarInFront" + "DiffToCarInFront", ] = 0 combined_df["DiffToCarInFront"] = combined_df.groupby("DriverNumber")["DiffToCarInFront"].ffill() combined_df["DiffToCarInFront"] = round(combined_df["DiffToCarInFront"] / 1000, 3) - combined_df["DiffToLeader"] = combined_df .sort_values(by=["SessionTimeTick", "LapsCompletion"], ascending=[True, False]).groupby(["SessionTimeTick"])["DiffToCarInFront"].cumsum() + combined_df["DiffToLeader"] = ( + combined_df.sort_values(by=["SessionTimeTick", "LapsCompletion"], ascending=[True, False]) + .groupby(["SessionTimeTick"])["DiffToCarInFront"] + .cumsum() + ) combined_df["DiffToLeader"] = round(combined_df["DiffToLeader"], 3) self.processed_pos_data = combined_df @@ -440,16 +487,13 @@ def add_in_pit_column(self) -> Self: df.loc[ ( - ( - df["PitInTimeMilliseconds"].notna() & - (df["PitInTimeMilliseconds"] <= df["SessionTimeMilliseconds"]) - ) | - ( - df["PitOutTimeMilliseconds"].notna() & - (df["PitOutTimeMilliseconds"] >= df["SessionTimeMilliseconds"]) + (df["PitInTimeMilliseconds"].notna() & (df["PitInTimeMilliseconds"] <= df["SessionTimeMilliseconds"])) + | ( + df["PitOutTimeMilliseconds"].notna() + & (df["PitOutTimeMilliseconds"] >= df["SessionTimeMilliseconds"]) ) ), - "InPit" + "InPit", ] = True df["InPit"] = df["InPit"].astype("boolean").fillna(False) @@ -476,7 +520,7 @@ def process_tire_compound_columns(self) -> Self: df.loc[df["Compound"] == "W", "CompoundColor"] = df.loc[df["Compound"] == "W", "WCompoundColor"] self.processed_pos_data = df.drop( - columns=["SCompoundColor", "MCompoundColor", "HCompoundColor", "ICompoundColor", "WCompoundColor"] + columns=["SCompoundColor", "MCompoundColor", "HCompoundColor", "ICompoundColor", "WCompoundColor"], ) return self diff --git a/src/f1p/services/data_extractor/enums.py b/src/f1p/services/data_extractor/enums.py index c331053..1193792 100644 --- a/src/f1p/services/data_extractor/enums.py +++ b/src/f1p/services/data_extractor/enums.py @@ -12,6 +12,7 @@ class ConventionalSessionIdentifiers(Enum): def all_values() -> list[str]: return [member.value for member in ConventionalSessionIdentifiers] + class SprintQualifyingSessionIdentifiers(Enum): FREE_PRACTICE_1 = "Practice 1" SPRINT_QUALIFYING = "Sprint Qualifying" @@ -21,4 +22,4 @@ class SprintQualifyingSessionIdentifiers(Enum): @staticmethod def all_values() -> list[str]: - return [member.value for member in SprintQualifyingSessionIdentifiers] \ No newline at end of file + return [member.value for member in SprintQualifyingSessionIdentifiers] diff --git a/src/f1p/ui/components/driver.py b/src/f1p/ui/components/driver.py index b1a9e02..623cc33 100644 --- a/src/f1p/ui/components/driver.py +++ b/src/f1p/ui/components/driver.py @@ -1,12 +1,12 @@ from decimal import Decimal from direct.showbase.DirectObject import DirectObject -from fastf1.core import Lap, Telemetry, Laps -from panda3d.core import NodePath, LVecBase4f +from fastf1.core import Lap, Laps, Telemetry +from panda3d.core import LVecBase4f, NodePath from pandas import Series, Timedelta -from f1p.services.procedural3d import SphereMaker from f1p.utils.color import hex_to_rgb_saturation +from procedural3d import SphereMaker class Driver(DirectObject): @@ -37,7 +37,7 @@ def __init__( self.team_name = team_name self.team_color = team_color self.pos_data = pos_data - self.ticks = self.pos_data.set_index("SessionTimeTick").to_dict(orient='index') + self.ticks = self.pos_data.set_index("SessionTimeTick").to_dict(orient="index") self.current_lap = current_lap self.current_lap_time = current_lap_time self.time_to_car_in_front = time_to_car_in_front diff --git a/src/f1p/ui/components/gui/button.py b/src/f1p/ui/components/gui/button.py index 964702d..26b0ef4 100644 --- a/src/f1p/ui/components/gui/button.py +++ b/src/f1p/ui/components/gui/button.py @@ -15,4 +15,4 @@ def __init__(self, parent=None, **kwargs): DirectButton.__init__(self, parent, **kwargs) - self.initialiseoptions(BlackButton) \ No newline at end of file + self.initialiseoptions(BlackButton) diff --git a/src/f1p/ui/components/gui/drop_down.py b/src/f1p/ui/components/gui/drop_down.py index ec8a212..0193ec9 100644 --- a/src/f1p/ui/components/gui/drop_down.py +++ b/src/f1p/ui/components/gui/drop_down.py @@ -1,7 +1,7 @@ from direct.gui.DirectFrame import DirectFrame -from direct.gui.DirectGuiGlobals import RAISED, B1RELEASE, WITHIN, WITHOUT +from direct.gui.DirectGuiGlobals import B1RELEASE, RAISED, WITHIN, WITHOUT from direct.gui.DirectOptionMenu import DirectOptionMenu -from panda3d.core import StaticTextFont, NodePath, TextNode +from panda3d.core import NodePath, StaticTextFont, TextNode from f1p.ui.components.gui.button import BlackButton @@ -15,7 +15,6 @@ def __init__( font: StaticTextFont | None = None, font_scale: float = 1.0, popup_menu_below: bool = True, - scale: float = 1.0, item_scale: float = 1.0, frameColor: tuple[float, float, float, float] = (0.15, 0.15, 0.15, 1), @@ -25,9 +24,7 @@ def __init__( pressEffect: int = 1, text_fg: tuple[float, float, float, float] = (1, 1, 1, 1), text_pos: tuple[float, float] = (0, 0), - items: list[str] | None = None, - **kwargs, ): self.width = width @@ -94,11 +91,11 @@ def setItems(self): Create new popup menu to reflect specified set of items """ # Remove old component if it exits - if self.popupMenu != None: - self.destroycomponent('popupMenu') + if self.popupMenu is not None: + self.destroycomponent("popupMenu") # Create new component self.popupMenu = self.createcomponent( - 'popupMenu', + "popupMenu", (), None, DirectFrame, @@ -107,19 +104,19 @@ def setItems(self): relief=RAISED, ) # Make sure it is on top of all the other gui widgets - self.popupMenu.setBin('gui-popup', 0) + self.popupMenu.setBin("gui-popup", 0) self.highlightedIndex = None - if not self['items']: + if not self["items"]: return # Create a new component for each item # Find the maximum extents of all items itemIndex = 0 self.minX = self.maxX = self.minZ = self.maxZ = None - for item in self['items']: + for item in self["items"]: c = self.createcomponent( - 'item%d' % itemIndex, + "item%d" % itemIndex, (), - 'item', + "item", BlackButton, (self.popupMenu,), text=item, @@ -133,7 +130,7 @@ def setItems(self): relief=RAISED, borderWidth=self.item_border_width, pressEffect=self.item_press_effect, - command=lambda i=itemIndex: self.set(i) + command=lambda i=itemIndex: self.set(i), ) bounds = c.getBounds() @@ -159,32 +156,31 @@ def setItems(self): self.maxHeight = self.maxZ - self.minZ # Adjust frame size for each item and bind actions to mouse events for i in range(itemIndex): - item = self.component('item%d' % i) + item = self.component("item%d" % i) # So entire extent of item's slot on popup is reactive to mouse - item['frameSize'] = self.item_frame_size + item["frameSize"] = self.item_frame_size # Move it to its correct position on the popup item.setPos(0, 0, (i * (-self.height * self.item_scale))) item.bind(B1RELEASE, self.hidePopupMenu) # Highlight background when mouse is in item - item.bind(WITHIN, lambda x, i=i, item=item: self._highlightItem(item, i)) + item.bind(WITHIN, lambda _, index=i, the_item=item: self._highlightItem(the_item, index)) # Restore specified color upon exiting - fc = item['frameColor'] - item.bind(WITHOUT, lambda x, item=item, fc=fc: self._unhighlightItem(item, fc)) + fc = item["frameColor"] + item.bind(WITHOUT, lambda _, the_item=item, frame_color=fc: self._unhighlightItem(the_item, frame_color)) # Set popup menu frame size to encompass all items - f = self.component('popupMenu') - f['frameSize'] = (0, self.width, -self.maxHeight * itemIndex, 0) + f = self.component("popupMenu") + f["frameSize"] = (0, self.width, -self.maxHeight * itemIndex, 0) # Determine what initial item to display and set text accordingly - if self['initialitem']: - self.set(self['initialitem'], fCommand=0) + if self["initialitem"]: + self.set(self["initialitem"], fCommand=0) else: # No initial item specified, just use first item self.set(0, fCommand=0) # Position popup Marker to the right of the button pm = self.popupMarker - pmw = (pm.getWidth() * pm.getScale()[0] + - 2 * self['popupMarkerBorder'][0]) + pmw = pm.getWidth() * pm.getScale()[0] + 2 * self["popupMarkerBorder"][0] if self.initFrameSize: # Use specified frame size bounds = list(self.initFrameSize) @@ -201,7 +197,7 @@ def setItems(self): # Adjust popup menu button to fit all items (or use user specified # frame size bounds[1] += pmw - self['frameSize'] = (bounds[0], bounds[1], bounds[2], bounds[3]) + self["frameSize"] = (bounds[0], bounds[1], bounds[2], bounds[3]) # Set initial state self.hidePopupMenu() @@ -216,14 +212,14 @@ def showPopupMenu(self, event=None): self.popupMenu.setZ(z_coordinate) def _highlightItem(self, item, index): - self._prevItemTextScale = item['text_scale'] - item['frameColor'] = self.highlight_color - item['frameSize'] = self.item_frame_size - item['text_scale'] = (self.item_text_scale, self.item_text_scale) # OVERRODE THE INSANITY THAT WAS THIS LINE + self._prevItemTextScale = item["text_scale"] + item["frameColor"] = self.highlight_color + item["frameSize"] = self.item_frame_size + item["text_scale"] = (self.item_text_scale, self.item_text_scale) # OVERRODE THE INSANITY THAT WAS THIS LINE self.highlightedIndex = index def _unhighlightItem(self, item, frameColor): - item['frameColor'] = frameColor - item['frameSize'] = self.item_frame_size - item['text_scale'] = self._prevItemTextScale + item["frameColor"] = frameColor + item["frameSize"] = self.item_frame_size + item["text_scale"] = self._prevItemTextScale self.highlightedIndex = None diff --git a/src/f1p/ui/components/leaderboard/__init__.py b/src/f1p/ui/components/leaderboard/__init__.py index 6d24654..3079d00 100644 --- a/src/f1p/ui/components/leaderboard/__init__.py +++ b/src/f1p/ui/components/leaderboard/__init__.py @@ -3,13 +3,17 @@ from direct.gui.OnscreenText import OnscreenText from direct.showbase.DirectObject import DirectObject from fastf1.core import Laps -from panda3d.core import StaticTextFont, Point3, TransparencyAttrib, TextNode +from panda3d.core import Point3, StaticTextFont, TextNode, TransparencyAttrib from f1p.services.data_extractor import DataExtractorService from f1p.ui.components.driver import Driver from f1p.ui.components.gui.drop_down import BlackDropDown -from f1p.ui.components.leaderboard.processors import LeaderboardProcessor, IntervalLeaderboardProcessor, \ - TiresLeaderboardProcessor, LeaderLeaderboardProcessor +from f1p.ui.components.leaderboard.processors import ( + IntervalLeaderboardProcessor, + LeaderboardProcessor, + LeaderLeaderboardProcessor, + TiresLeaderboardProcessor, +) from f1p.ui.components.map import Map @@ -20,7 +24,7 @@ def __init__( symbols_font: StaticTextFont, text_font: StaticTextFont, circuit_map: Map, - data_extractor: DataExtractorService + data_extractor: DataExtractorService, ): super().__init__() @@ -75,7 +79,7 @@ def render_frame(self) -> None: parent=self.pixel2d, frameColor=(0.20, 0.20, 0.20, 0.7), frameSize=(0, self.width, 0, -self.height), - pos=Point3(20, 0, -50) + pos=Point3(20, 0, -50), ) def render_track_status_frame(self) -> None: @@ -83,19 +87,19 @@ def render_track_status_frame(self) -> None: parent=self.frame, frameColor=self.data_extractor.green_flag_track_status_color, frameSize=(0, self.width - 5, 0, -1), - pos=Point3(2, 0, -2) + pos=Point3(2, 0, -2), ) self.track_status_frame_left = DirectFrame( parent=self.frame, frameColor=self.data_extractor.green_flag_track_status_color, frameSize=(0, 1, 0, self.width - 5), - pos=Point3(2, 0, -self.width + 3) + pos=Point3(2, 0, -self.width + 3), ) def render_f1_logo(self) -> None: self.f1_logo = OnscreenImage( - image='./src/f1p/ui/images/f1_logo.png', + image="./src/f1p/ui/images/f1_logo.png", pos=Point3(self.width / 2, 0, -27), scale=self.width / 4, parent=self.frame, @@ -107,7 +111,7 @@ def render_lap_counter(self) -> None: parent=self.frame, frameColor=(0.1, 0.1, 0.1, 0.7), frameSize=(0, self.width, 0, 40), - pos=Point3(0, 0, -90) + pos=Point3(0, 0, -90), ) self.lap_counter = OnscreenText( @@ -116,7 +120,7 @@ def render_lap_counter(self) -> None: scale=self.width / 10, fg=(1, 1, 1, 0.8), font=self.text_font, - text=f'LAP 1/{self.total_laps}', + text=f"LAP 1/{self.total_laps}", ) def render_track_status(self) -> None: @@ -124,7 +128,7 @@ def render_track_status(self) -> None: parent=self.frame, frameColor=self.data_extractor.green_flag_track_status_color, frameSize=(0, self.width - 2, 0, 30), - pos=Point3(2, 0, -120) + pos=Point3(2, 0, -120), ) self.track_status = OnscreenText( @@ -146,23 +150,25 @@ def switch_mode(self, mode: str) -> None: self.mode = "tires" def render_mode_selector(self) -> None: - BlackDropDown( - parent=self.frame, - width=40, - height=30, - font=self.symbols_font, - font_scale=20, - popup_menu_below=True, - command=self.switch_mode, - text="leaderboard", - text_pos=(20, -5), - text_align=TextNode.ACenter, - item_text_align=TextNode.ACenter, - items=["🕒", "🕘", "⛁"], - item_scale=1.0, - initialitem=0, - pos=Point3(self.width - 45, 0, -22), - ), + ( + BlackDropDown( + parent=self.frame, + width=40, + height=30, + font=self.symbols_font, + font_scale=20, + popup_menu_below=True, + command=self.switch_mode, + text="leaderboard", + text_pos=(20, -5), + text_align=TextNode.ACenter, + item_text_align=TextNode.ACenter, + items=["🕒", "🕘", "⛁"], + item_scale=1.0, + initialitem=0, + pos=Point3(self.width - 45, 0, -22), + ), + ) def render_drivers(self) -> None: offset_from_top = 140 @@ -176,7 +182,7 @@ def render_drivers(self) -> None: fg=(1, 1, 1, 0.8), font=self.symbols_font, text="", - ) + ), ) OnscreenText( @@ -193,8 +199,8 @@ def render_drivers(self) -> None: parent=self.frame, frameColor=driver.team_color_obj, frameSize=(0, 12, 0, 12), - pos=Point3(40, 0, -offset_from_top - 2 - (index * 23)) - ) + pos=Point3(40, 0, -offset_from_top - 2 - (index * 23)), + ), ) self.driver_abbreviations.append( @@ -205,18 +211,18 @@ def render_drivers(self) -> None: fg=(1, 1, 1, 0.8), font=self.text_font, text=driver.abbreviation, - ) + ), ) self.driver_times.append( - OnscreenText( + OnscreenText( parent=self.frame, pos=(145, -offset_from_top - (index * 23)), scale=self.width / 14, fg=(1, 1, 1, 0.8), font=self.text_font, text="NO TIME", - ) + ), ) self.driver_tires.append( @@ -227,7 +233,7 @@ def render_drivers(self) -> None: fg=(1, 0, 0, 0.8), font=self.text_font, text="S", - ) + ), ) has_fastest_lap = OnscreenText( diff --git a/src/f1p/ui/components/leaderboard/processors/__init__.py b/src/f1p/ui/components/leaderboard/processors/__init__.py index f842c3c..3c1bb5f 100644 --- a/src/f1p/ui/components/leaderboard/processors/__init__.py +++ b/src/f1p/ui/components/leaderboard/processors/__init__.py @@ -38,8 +38,7 @@ def __init__( self.has_fastest_lap = has_fastest_lap self.data_extractor = data_extractor - def update_driver(self, driver: Driver, current_record: Series, index: int) -> None: - ... + def update_driver(self, driver: Driver, current_record: Series, index: int) -> None: ... def update(self, session_time_tick: int) -> None: total_laps = self.data_extractor.total_laps diff --git a/src/f1p/ui/components/map.py b/src/f1p/ui/components/map.py index 90b93fa..61a44b7 100644 --- a/src/f1p/ui/components/map.py +++ b/src/f1p/ui/components/map.py @@ -34,7 +34,7 @@ def render_map(self, df: DataFrame) -> None: dx = np.gradient(track_x) dy = np.gradient(track_y) - norm = np.sqrt(dx ** 2 + dy ** 2) + norm = np.sqrt(dx**2 + dy**2) norm[norm == 0] = 10 dx /= norm dy /= norm @@ -49,14 +49,14 @@ def render_map(self, df: DataFrame) -> None: inner_df = new_df.copy() inner_df["X"] = x_inner inner_df["Y"] = y_inner - inner_track = inner_df.loc[:, ('X', 'Y', 'Z')].to_numpy() + inner_track = inner_df.loc[:, ("X", "Y", "Z")].to_numpy() self.inner_border_node_path = self.draw_track(inner_track, (0.9, 0.9, 0.9, 1)) self.inner_border_node_path.reparentTo(self.parent) outer_df = new_df.copy() outer_df["X"] = x_outer outer_df["Y"] = y_outer - outer_track = outer_df.loc[:, ('X', 'Y', 'Z')].to_numpy() + outer_track = outer_df.loc[:, ("X", "Y", "Z")].to_numpy() self.outer_border_node_path = self.draw_track(outer_track, (0.9, 0.9, 0.9, 1)) self.outer_border_node_path.reparentTo(self.parent) diff --git a/src/f1p/ui/components/menu.py b/src/f1p/ui/components/menu.py index b984744..8b31a16 100644 --- a/src/f1p/ui/components/menu.py +++ b/src/f1p/ui/components/menu.py @@ -6,12 +6,19 @@ from panda3d.core import Point3, StaticTextFont from f1p.services.data_extractor import DataExtractorService -from f1p.services.data_extractor.enums import SprintQualifyingSessionIdentifiers, ConventionalSessionIdentifiers +from f1p.services.data_extractor.enums import ConventionalSessionIdentifiers, SprintQualifyingSessionIdentifiers from f1p.ui.components.gui.drop_down import BlackDropDown class Menu: - def __init__(self, pixel2d, width: int, height: int, text_font: StaticTextFont, data_extractor: DataExtractorService): + def __init__( + self, + pixel2d, + width: int, + height: int, + text_font: StaticTextFont, + data_extractor: DataExtractorService, + ): self.pixel2d = pixel2d self.width = width self.height = height @@ -62,7 +69,7 @@ def render_year_menu(self) -> None: items=["Year"] + [str(year) for year in range(2018, self.current_year + 1)], item_scale=0.7, initialitem=0, - pos=Point3(0, 0, -self.height / 2) + pos=Point3(0, 0, -self.height / 2), ) self.year_menu["command"] = self.select_year @@ -77,7 +84,7 @@ def select_event(self, event_name: str) -> None: self.data_extractor.event_name = event_name event = self.data_extractor.event - match event['EventFormat']: + match event["EventFormat"]: case "sprint_qualifying": self.session_menu["items"] = ["Session"] + SprintQualifyingSessionIdentifiers.all_values() case "conventional": @@ -96,7 +103,7 @@ def render_events_menu(self) -> None: items=["Event"], item_scale=0.7, initialitem=0, - pos=Point3(76, 0, -self.height / 2) + pos=Point3(76, 0, -self.height / 2), ) def select_session(self, session_id: str) -> None: @@ -121,7 +128,7 @@ def render_session_menu(self) -> None: items=["Session"], item_scale=0.7, initialitem=0, - pos=Point3(606, 0, -self.height / 2) + pos=Point3(606, 0, -self.height / 2), ) def render(self): diff --git a/src/f1p/ui/components/playback.py b/src/f1p/ui/components/playback.py index 17b3b70..a990d0b 100644 --- a/src/f1p/ui/components/playback.py +++ b/src/f1p/ui/components/playback.py @@ -1,4 +1,4 @@ -from math import sin, cos +from math import cos, sin from direct.gui.DirectButton import DirectButton from direct.gui.DirectFrame import DirectFrame @@ -7,13 +7,11 @@ from direct.showbase.DirectObject import DirectObject from direct.showbase.MessengerGlobal import messenger from direct.task.Task import TaskManager -from panda3d.core import Point3, StaticTextFont, Camera, deg2Rad, TextNode +from panda3d.core import Camera, Point3, StaticTextFont, TextNode, deg2Rad from f1p.services.data_extractor import DataExtractorService from f1p.ui.components.gui.button import BlackButton from f1p.ui.components.gui.drop_down import BlackDropDown -from f1p.ui.components.leaderboard import Leaderboard -from f1p.ui.components.map import Map class PlaybackControls(DirectObject): @@ -27,7 +25,7 @@ def __init__( height: int, symbols_font: StaticTextFont, text_font: StaticTextFont, - data_extractor: DataExtractorService + data_extractor: DataExtractorService, ): super().__init__() @@ -60,7 +58,7 @@ def render_frame(self) -> None: parent=self.pixel2d, frameColor=(0.18, 0.18, 0.18, 1), frameSize=(0, self.width, 0, -self.height), - pos=Point3(0, 0, self.height - self.window_height) + pos=Point3(0, 0, self.height - self.window_height), ) def move_timeline(self, task): @@ -94,7 +92,7 @@ def render_play_button(self) -> None: text_scale=self.height - 5, text_align=TextNode.ACenter, text_pos=(-2, (-self.height / 2) + 7), - pos=Point3(17, 0, -self.height / 2) + pos=Point3(17, 0, -self.height / 2), ) def update_components(self) -> None: @@ -120,7 +118,7 @@ def render_timeline(self) -> None: frameColor=record.Color, frameSize=(0, record.Width, 0, -3), pos=Point3(record.PixelStart, 0, 0), - ) + ), ) self.timeline = DirectSlider( @@ -128,7 +126,7 @@ def render_timeline(self) -> None: value=1, range=(1, self.data_extractor.session_ticks), pageSize=1, - frameSize=(0, self.width - 121, -self.height/ 2, self.height/ 2), + frameSize=(0, self.width - 121, -self.height / 2, self.height / 2), frameColor=(0.15, 0.15, 0.15, 1), thumb_frameSize=(0, 5, -self.height / 2, self.height / 2), thumb_frameColor=(0.1, 0.1, 0.1, 1), @@ -137,7 +135,7 @@ def render_timeline(self) -> None: text_scale=self.height, text_fg=(1, 1, 1, 1), text_pos=(-2, (-self.height / 2) + 7), - pos=Point3(34, 0, -self.height / 2) + pos=Point3(34, 0, -self.height / 2), ) def change_playback_speed(self, playback_speed: str) -> None: @@ -165,7 +163,7 @@ def render_playback_speed_button(self) -> None: items=["x3", "x5", "x10"], item_scale=1.0, initialitem=0, - pos=Point3(self.width - 87, 0, -self.height / 2) + pos=Point3(self.width - 87, 0, -self.height / 2), ) def move_camera(self, task): @@ -213,7 +211,7 @@ def render_camera_button(self) -> None: items=["🌎", "🗺"], item_scale=1.0, initialitem=0, - pos=Point3(self.width - 40, 0, -self.height / 2) + pos=Point3(self.width - 40, 0, -self.height / 2), ) def render(self): diff --git a/src/f1p/utils/color.py b/src/f1p/utils/color.py index ebcb965..9651ddb 100644 --- a/src/f1p/utils/color.py +++ b/src/f1p/utils/color.py @@ -3,19 +3,19 @@ def hex_to_rgb_saturation(hex_code): # 1. Convert hex to RGB (0-255 range) - hex_code = hex_code.lstrip('#') + hex_code = hex_code.lstrip("#") lv = len(hex_code) - rgb_255 = tuple(int(hex_code[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + rgb_255 = tuple(int(hex_code[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) # 2. Normalize RGB to the [0.0, 1.0] range r_norm, g_norm, b_norm = [x / 255.0 for x in rgb_255] # 3. Convert normalized RGB to HLS (Hue, Lightness, Saturation) # The result 's' is the saturation, ranging from 0.0 (grayscale) to 1.0 (full color) - h, l, s = colorsys.rgb_to_hls(r_norm, g_norm, b_norm) + _, _, saturation = colorsys.rgb_to_hls(r_norm, g_norm, b_norm) return { "hex": f"#{hex_code}", "rgb": rgb_255, - "saturation_hls": s - } \ No newline at end of file + "saturation_hls": saturation, + } diff --git a/src/f1p/utils/geometry.py b/src/f1p/utils/geometry.py index c7a7af6..ce012ca 100644 --- a/src/f1p/utils/geometry.py +++ b/src/f1p/utils/geometry.py @@ -10,6 +10,7 @@ def scale(df: DataFrame, factor: float) -> DataFrame: return new_df + def shift(df: DataFrame, direction: str, amount: float) -> DataFrame: new_df = df.copy() new_df[direction] = new_df[direction] + amount @@ -18,14 +19,14 @@ def shift(df: DataFrame, direction: str, amount: float) -> DataFrame: def rotate(df: DataFrame, radians: float) -> DataFrame: - coordinates = df.loc[:, ('X', 'Y', 'Z')].to_numpy() + coordinates = df.loc[:, ("X", "Y", "Z")].to_numpy() rot_mat = np.array( [ [np.cos(radians), np.sin(radians), 0], [-np.sin(radians), np.cos(radians), 0], [0, 0, 1], - ] + ], ) rotated_coordinates = np.matmul(coordinates, rot_mat) @@ -34,9 +35,10 @@ def rotate(df: DataFrame, radians: float) -> DataFrame: "X": rotated_coordinates[:, 0], "Y": rotated_coordinates[:, 1], "Z": rotated_coordinates[:, 2], - } + }, ) + def find_center(df: DataFrame) -> tuple[float, float, float]: return ( ((df["X"].max() - df["X"].min()) / 2) + df["X"].min(), @@ -44,10 +46,11 @@ def find_center(df: DataFrame) -> tuple[float, float, float]: ((df["Z"].max() - df["Z"].min()) / 2) + df["Z"].min(), ) + def resize_pos_data(rotation: float, pos_data_df: DataFrame) -> DataFrame: df = pos_data_df.copy() - coordinates_cols_only_df = df[['X', 'Y', 'Z']] + coordinates_cols_only_df = df[["X", "Y", "Z"]] rotated_coordinates_df = rotate(coordinates_cols_only_df, rotation) scaled_coordinates_df = scale(rotated_coordinates_df, 1 / 600) @@ -58,9 +61,10 @@ def resize_pos_data(rotation: float, pos_data_df: DataFrame) -> DataFrame: return df + def center_pos_data(map_center_coordinate: tuple[float, float, float], df: DataFrame) -> DataFrame: combined_pos_data_df = df.copy() - coordinates_cols_only_df = combined_pos_data_df[['X', 'Y', 'Z']] + coordinates_cols_only_df = combined_pos_data_df[["X", "Y", "Z"]] shifted_x_coordinates_df = shift(coordinates_cols_only_df, direction="X", amount=-map_center_coordinate[0]) shifted_y_coordinates_df = shift(coordinates_cols_only_df, direction="Y", amount=-map_center_coordinate[1]) diff --git a/src/f1p/utils/performance.py b/src/f1p/utils/performance.py index afa4a4a..6a11ee8 100644 --- a/src/f1p/utils/performance.py +++ b/src/f1p/utils/performance.py @@ -1,16 +1,19 @@ import time from functools import wraps + def timeit(func): """ Decorator to measure the execution time of a function. """ + @wraps(func) def wrapper(*args, **kwargs): start_time = time.perf_counter() # Record the start time - result = func(*args, **kwargs) # Execute the function - end_time = time.perf_counter() # Record the end time + result = func(*args, **kwargs) # Execute the function + end_time = time.perf_counter() # Record the end time execution_time = end_time - start_time - print(f"'{func.__name__}' took {execution_time:.4f} seconds to complete") + print(f"'{func.__name__}' took {execution_time:.4f} seconds to complete") # noqa: T201 return result - return wrapper \ No newline at end of file + + return wrapper diff --git a/src/f1p/services/procedural3d/__init__.py b/src/procedural3d/__init__.py similarity index 100% rename from src/f1p/services/procedural3d/__init__.py rename to src/procedural3d/__init__.py diff --git a/src/f1p/services/procedural3d/base.py b/src/procedural3d/base.py similarity index 100% rename from src/f1p/services/procedural3d/base.py rename to src/procedural3d/base.py diff --git a/src/f1p/services/procedural3d/box.py b/src/procedural3d/box.py similarity index 100% rename from src/f1p/services/procedural3d/box.py rename to src/procedural3d/box.py diff --git a/src/f1p/services/procedural3d/cone.py b/src/procedural3d/cone.py similarity index 100% rename from src/f1p/services/procedural3d/cone.py rename to src/procedural3d/cone.py diff --git a/src/f1p/services/procedural3d/cylinder.py b/src/procedural3d/cylinder.py similarity index 100% rename from src/f1p/services/procedural3d/cylinder.py rename to src/procedural3d/cylinder.py diff --git a/src/f1p/services/procedural3d/sphere.py b/src/procedural3d/sphere.py similarity index 100% rename from src/f1p/services/procedural3d/sphere.py rename to src/procedural3d/sphere.py diff --git a/src/f1p/services/procedural3d/torus.py b/src/procedural3d/torus.py similarity index 100% rename from src/f1p/services/procedural3d/torus.py rename to src/procedural3d/torus.py diff --git a/tests/f1p/__init__.py b/tests/f1p/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/f1p/services/__init__.py b/tests/f1p/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/f1p/services/data_extractor/__init__.py b/tests/f1p/services/data_extractor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/f1p/services/data_extractor/enums/__init__.py b/tests/f1p/services/data_extractor/enums/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/f1p/services/data_extractor/enums/test_conventional_session_identifiers.py b/tests/f1p/services/data_extractor/enums/test_conventional_session_identifiers.py new file mode 100644 index 0000000..34d031f --- /dev/null +++ b/tests/f1p/services/data_extractor/enums/test_conventional_session_identifiers.py @@ -0,0 +1,22 @@ +from enum import Enum + +import pytest + +from f1p.services.data_extractor.enums import ConventionalSessionIdentifiers + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + ("Practice 1", ConventionalSessionIdentifiers.FREE_PRACTICE_1), + ("Practice 2", ConventionalSessionIdentifiers.FREE_PRACTICE_2), + ("Practice 3", ConventionalSessionIdentifiers.FREE_PRACTICE_3), + ("Qualifying", ConventionalSessionIdentifiers.QUALIFYING), + ("Race", ConventionalSessionIdentifiers.RACE), + ], +) +def test_initialization(value: str, expected: ConventionalSessionIdentifiers) -> None: + enum = ConventionalSessionIdentifiers(value) + + assert isinstance(enum, Enum) + assert enum == expected