diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..a63ff59 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,181 @@ +# This file is autogenerated by maturin v1.10.2 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + - runner: ubuntu-22.04 + target: s390x + - runner: ubuntu-22.04 + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + architecture: ${{ matrix.platform.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-13 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v4 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v2 + with: + subject-path: 'wheels-*/*' + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b8a3c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# Rust +/target +**/*.rs.bk +Cargo.lock.lock + +# Python +__pycache__/ +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.benchmarks/ +*.py[cod] +*$py.class +*.so +.Python +.venv/ +env/ +venv/ +ENV/ +dist/ +build/ +*.egg-info/ +*.egg +.installed.cfg +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json +.python-version + +# Jupyter Notebook +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# Coverage / CI +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Documentation +docs/_build/ +site/ + +# IDEs +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +*.log + +# Project Specific +data/ +examples/data/ +python/tracking_data_container/lib_tracking_data_container.dylib.* +python/tracking_data_container/libtracking_data_container.dylib.* +python/tracking_data_container/*.dylib +python/tracking_data_container/*.so diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b07ebd1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer +- repo: http://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: cargo-check +- repo: local + hooks: + - id: lint + name: Lint + entry: make lint + types_or: [python, rust] + language: system + pass_filenames: false + - id: format + name: Format + entry: make format + types_or: [python, rust] + language: system + pass_filenames: false diff --git a/.rust-analyzer.toml b/.rust-analyzer.toml new file mode 100644 index 0000000..8a79197 --- /dev/null +++ b/.rust-analyzer.toml @@ -0,0 +1,2 @@ +[cargo] +features = ["python"] diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c74a2fe --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3432 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arrow" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ec52ba94edeed950e4a41f75d35376df196e8cb04437f7280a5aa49f20f796" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc766fdacaf804cb10c7c70580254fcdb5d55cdfda2bc57b02baf5223a3af9e" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num", +] + +[[package]] +name = "arrow-array" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12fcdb3f1d03f69d3ec26ac67645a8fe3f878d77b5ebb0b15d64a116c212985" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.15.5", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263f4801ff1839ef53ebd06f99a56cecd1dbaf314ec893d93168e2e860e0291c" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede6175fbc039dfc946a61c1b6d42fd682fcecf5ab5d148fbe7667705798cac9" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1644877d8bc9a0ef022d9153dc29375c2bda244c39aec05a91d0e87ccf77995f" +dependencies = [ + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "lazy_static", + "regex", +] + +[[package]] +name = "arrow-data" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ipc" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ff528658b521e33905334723b795ee56b393dbe9cf76c8b1f64b648c65a60c" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "flatbuffers", + "lz4_flex", +] + +[[package]] +name = "arrow-json" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee5b4ca98a7fb2efb9ab3309a5d1c88b5116997ff93f3147efdc1062a6158e9" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap", + "lexical-core", + "memchr", + "num", + "serde", + "serde_json", + "simdutf8", +] + +[[package]] +name = "arrow-ord" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a3334a743bd2a1479dbc635540617a3923b4b2f6870f37357339e6b5363c21" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", +] + +[[package]] +name = "arrow-row" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d1d7a7291d2c5107e92140f75257a99343956871f3d3ab33a7b41532f79cb68" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "arrow-select" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69efcd706420e52cd44f5c4358d279801993846d1c2a8e52111853d61d55a619" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21546b337ab304a32cfc0770f671db7411787586b45b78b4593ae78e64e2b03" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", +] + +[[package]] +name = "async-compression" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" +dependencies = [ + "bzip2", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "xz2", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "comfy-table" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +dependencies = [ + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "datafusion" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae420e7a5b0b7f1c39364cc76cbcd0f5fdc416b2514ae3847c2676bbd60702a" +dependencies = [ + "arrow", + "arrow-array", + "arrow-ipc", + "arrow-schema", + "async-compression", + "async-trait", + "bytes", + "bzip2", + "chrono", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-nested", + "datafusion-functions-table", + "datafusion-functions-window", + "datafusion-optimizer", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-sql", + "flate2", + "futures", + "glob", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot", + "parquet", + "rand", + "regex", + "sqlparser", + "tempfile", + "tokio", + "tokio-util", + "url", + "uuid", + "xz2", + "zstd", +] + +[[package]] +name = "datafusion-catalog" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f27987bc22b810939e8dfecc55571e9d50355d6ea8ec1c47af8383a76a6d0e1" +dependencies = [ + "arrow", + "async-trait", + "dashmap", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", + "datafusion-sql", + "futures", + "itertools 0.14.0", + "log", + "parking_lot", + "sqlparser", +] + +[[package]] +name = "datafusion-common" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3f6d5b8c9408cc692f7c194b8aa0c0f9b253e065a8d960ad9cdc2a13e697602" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ipc", + "arrow-schema", + "base64", + "half", + "hashbrown 0.14.5", + "indexmap", + "libc", + "log", + "object_store", + "parquet", + "paste", + "recursive", + "sqlparser", + "tokio", + "web-time", +] + +[[package]] +name = "datafusion-common-runtime" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4603c8e8a4baf77660ab7074cc66fc15cc8a18f2ce9dfadb755fc6ee294e48" +dependencies = [ + "log", + "tokio", +] + +[[package]] +name = "datafusion-doc" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bf4bc68623a5cf231eed601ed6eb41f46a37c4d15d11a0bff24cbc8396cd66" + +[[package]] +name = "datafusion-execution" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b491c012cdf8e051053426013429a76f74ee3c2db68496c79c323ca1084d27" +dependencies = [ + "arrow", + "dashmap", + "datafusion-common", + "datafusion-expr", + "futures", + "log", + "object_store", + "parking_lot", + "rand", + "tempfile", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a181408d4fc5dc22f9252781a8f39f2d0e5d1b33ec9bde242844980a2689c1" +dependencies = [ + "arrow", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr-common", + "indexmap", + "paste", + "recursive", + "serde_json", + "sqlparser", +] + +[[package]] +name = "datafusion-expr-common" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1129b48e8534d8c03c6543bcdccef0b55c8ac0c1272a15a56c67068b6eb1885" +dependencies = [ + "arrow", + "datafusion-common", + "itertools 0.14.0", + "paste", +] + +[[package]] +name = "datafusion-functions" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125874e4856dfb09b59886784fcb74cde5cfc5930b3a80a1a728ef7a010df6b" +dependencies = [ + "arrow", + "arrow-buffer", + "base64", + "blake2", + "blake3", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-macros", + "hashbrown 0.14.5", + "hex", + "itertools 0.14.0", + "log", + "md-5", + "rand", + "regex", + "sha2", + "unicode-segmentation", + "uuid", +] + +[[package]] +name = "datafusion-functions-aggregate" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3add7b1d3888e05e7c95f2b281af900ca69ebdcb21069ba679b33bde8b3b9d6" +dependencies = [ + "ahash", + "arrow", + "arrow-buffer", + "arrow-schema", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "half", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-aggregate-common" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e18baa4cfc3d2f144f74148ed68a1f92337f5072b6dde204a0dbbdf3324989c" +dependencies = [ + "ahash", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-functions-nested" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec5ee8cecb0dc370291279673097ddabec03a011f73f30d7f1096457127e03e" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-macros", + "datafusion-physical-expr-common", + "itertools 0.14.0", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-table" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c403ddd473bbb0952ba880008428b3c7febf0ed3ce1eec35a205db20efb2a36" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot", + "paste", +] + +[[package]] +name = "datafusion-functions-window" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab18c2fb835614d06a75f24a9e09136d3a8c12a92d97c95a6af316a1787a9c5" +dependencies = [ + "datafusion-common", + "datafusion-doc", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-window-common" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77b73bc15e7d1967121fdc7a55d819bfb9d6c03766a6c322247dce9094a53a4" +dependencies = [ + "datafusion-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-macros" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09369b8d962291e808977cf94d495fd8b5b38647232d7ef562c27ac0f495b0af" +dependencies = [ + "datafusion-expr", + "quote", + "syn", +] + +[[package]] +name = "datafusion-optimizer" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2403a7e4a84637f3de7d8d4d7a9ccc0cc4be92d89b0161ba3ee5be82f0531c54" +dependencies = [ + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-expr", + "indexmap", + "itertools 0.14.0", + "log", + "recursive", + "regex", + "regex-syntax", +] + +[[package]] +name = "datafusion-physical-expr" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ff72ac702b62dbf2650c4e1d715ebd3e4aab14e3885e72e8549e250307347c" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-schema", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr-common", + "half", + "hashbrown 0.14.5", + "indexmap", + "itertools 0.14.0", + "log", + "paste", + "petgraph", +] + +[[package]] +name = "datafusion-physical-expr-common" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60982b7d684e25579ee29754b4333057ed62e2cc925383c5f0bd8cab7962f435" +dependencies = [ + "ahash", + "arrow", + "arrow-buffer", + "datafusion-common", + "datafusion-expr-common", + "hashbrown 0.14.5", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5e85c189d5238a5cf181a624e450c4cd4c66ac77ca551d6f3ff9080bac90bb" +dependencies = [ + "arrow", + "arrow-schema", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "futures", + "itertools 0.14.0", + "log", + "recursive", + "url", +] + +[[package]] +name = "datafusion-physical-plan" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36bf163956d7e2542657c78b3383fdc78f791317ef358a359feffcdb968106f" +dependencies = [ + "ahash", + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "futures", + "half", + "hashbrown 0.14.5", + "indexmap", + "itertools 0.14.0", + "log", + "parking_lot", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "datafusion-sql" +version = "45.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13caa4daede211ecec53c78b13c503b592794d125f9a3cc3afe992edf9e7f43" +dependencies = [ + "arrow", + "arrow-array", + "arrow-schema", + "bigdecimal", + "datafusion-common", + "datafusion-expr", + "indexmap", + "log", + "recursive", + "regex", + "sqlparser", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +dependencies = [ + "twox-hash 2.1.2", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "numpy" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78" +dependencies = [ + "half", + "libc", + "ndarray", + "num-complex", + "num-integer", + "num-traits", + "pyo3", + "rustc-hash", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cfccb68961a56facde1163f9319e0d15743352344e7808a11795fb99698dcaf" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "humantime", + "itertools 0.13.0", + "parking_lot", + "percent-encoding", + "snafu", + "tokio", + "tracing", + "url", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "parquet" +version = "54.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb15796ac6f56b429fd99e33ba133783ad75b27c36b4b5ce06f1f82cc97754e" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-ipc", + "arrow-schema", + "arrow-select", + "base64", + "brotli", + "bytes", + "chrono", + "flate2", + "futures", + "half", + "hashbrown 0.15.5", + "lz4_flex", + "num", + "num-bigint", + "object_store", + "paste", + "seq-macro", + "simdutf8", + "snap", + "thrift", + "tokio", + "twox-hash 1.6.3", + "zstd", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pyo3" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +dependencies = [ + "cfg-if", + "chrono", + "chrono-tz", + "indexmap", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-arrow" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e51f9bb00155f44cd5b1b87199d364613b0cbdf082982f5f0d754287d9b4686" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-schema", + "chrono", + "half", + "indexmap", + "numpy", + "pyo3", + "thiserror", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-log" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45192e5e4a4d2505587e27806c7b710c231c40c56f3bfc19535d0bb25df52264" +dependencies = [ + "arc-swap", + "log", + "pyo3", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "sqlparser" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8" +dependencies = [ + "log", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tdc-core" +version = "0.1.0-alpha.1" +dependencies = [ + "ahash", + "arrow", + "arrow-arith", + "arrow-array", + "arrow-cast", + "arrow-ord", + "arrow-schema", + "arrow-select", + "chrono", + "criterion", + "datafusion", + "log", + "num_cpus", + "once_cell", + "parking_lot", + "parquet", + "rayon", + "regex", + "serde", + "serde_json", + "strum", + "strum_macros", + "tokio", +] + +[[package]] +name = "tdc-ops" +version = "0.1.0-alpha.1" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-cast", + "arrow-ord", + "arrow-schema", + "arrow-select", + "pyo3", + "pyo3-arrow", +] + +[[package]] +name = "tdc-parsers" +version = "0.1.0-alpha.1" +dependencies = [ + "ahash", + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "criterion", + "fast-float", + "memmap2", + "pyo3", + "quick-xml", + "rayon", + "serde", + "serde_json", + "tdc-core", + "tempfile", +] + +[[package]] +name = "tdc-python" +version = "0.1.0-alpha.1" +dependencies = [ + "ahash", + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "datafusion", + "datafusion-functions", + "datafusion-functions-window", + "futures", + "num_cpus", + "once_cell", + "pyo3", + "pyo3-arrow", + "pyo3-log", + "serde", + "strum", + "tdc-core", + "tdc-ops", + "tdc-parsers", + "tokio", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "ordered-float", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d6efd31 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +members = [ + "crates/tdc-core", + "crates/tdc-ops", + "crates/tdc-parsers", + "crates/tdc-python", +] +resolver = "2" + +[workspace.package] +version = "0.1.0-alpha.1" +edition = "2024" +license = "MIT" +authors = ["Pieter Robberechts "] +repository = "https://github.com/probberechts/tracking-data-container" + +[profile.release] +codegen-units = 1 +lto = "thin" +strip = true diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..e37c36d --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,50 @@ +# Gemini Project: tracking-data-container + +This document provides a comprehensive overview of the `tracking-data-container` (TDC) project, designed to facilitate understanding and development. + +## Project Overview + +The `tracking-data-container` is a high-performance, metadata-aware container for sports tracking data. It implements the vision of a shared, standardized interface for the sports analytics ecosystem, backed by Apache Arrow and DataFusion. + +### Motivation & RFC +TDC aims to solve the challenges of fragmentation and duplication in sports tracking pipelines. It acts as the central interface between various tools (e.g., Kloppy, Floodlight, BallRadar), providing: +- **Scalability**: Overcoming the limitations of object-based models for large datasets. +- **Interoperability**: Standardized schemas and zero-copy data exchange. +- **Reproducibility**: Shared logic for derived metrics and transformations. + +### Core Architecture +- **Rust Core (`tdc-core`)**: Leverages Apache Arrow for in-memory data representation and DataFusion for query execution. It handles performance-critical operations like coordinate transformations and orientation normalization. +- **Python Bindings (`tdc-python`)**: Provides a high-level API for Python users, ensuring seamless integration with the data science ecosystem (Pandas, Polars, PyArrow). +- **Schema & CDF**: TDC aligns with the **Common Data Format (CDF)** standard for column naming (e.g., `ball/x`, `teams/{tid}/players/{pid}/x`). + +### Key Components +- **TrackingDataContainer**: The primary eager interface wrapping an Arrow `StructArray` and rich metadata. +- **LazyTrackingDataContainer**: A deferred execution model using DataFusion logical plans for lazy evaluation and filter pushdown. +- **DataField**: A domain-specific representation of a data column, supporting both hierarchical string paths and component-based construction. +- **TrackingDataBuilder**: An optimized ingestor for building containers from raw streams or diverse formats. +- **Frame**: A materialized single frame of data, supporting dual indexing by string name or `DataField`. + +## Building and Running + +The project uses a `Makefile` to streamline common development tasks. + +### Installation +```bash +make setup-dev +``` + +### Building +- **Development**: `make build-dev` +- **Production Wheel**: `make build-wheel` + +### Testing +- **Full Suite**: `make test` +- **Python Only**: `make test-python` +- **Rust Only**: `make test-rust` + +## Development Conventions + +- **Code Style**: `ruff` for Python, `cargo fmt` for Rust. +- **Type Hinting**: Full type hinting required for Python code (`mypy` compliant). +- **Metadata Handling**: Always prefer metadata-aware selectors (`container.selector`) over hardcoded strings. +- **Schema Compliance**: All changes should maintain compatibility with the CDF v0.2.2 specification. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab23d2d --- /dev/null +++ b/Makefile @@ -0,0 +1,118 @@ +.DEFAULT_GOAL := all +sources = python/tracking_data_container +test_sources = python/tests + +export CARGO_TERM_COLOR=$(shell (test -t 0 && echo always) || echo auto) + +.PHONY: .uv ## Check that uv is installed +.uv: + @uv -V || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/' + +.PHONY: install ## Install the package & dependencies with debug build +install: .uv + uv sync --frozen --group all + uv run maturin develop --uv -E pyarrow,pandas,polars + +.PHONY: install-prod ## Install the package & dependencies with release build +install-prod: .uv + uv sync --frozen --group all + uv run maturin develop --uv --release -E pyarrow,pandas,polars + +.PHONY: setup-dev ## First-time setup: install + pre-commit hooks +setup-dev: install + uv run pre-commit install --install-hooks + +.PHONY: rebuild-lockfiles ## Rebuild lockfiles from scratch, updating all dependencies +rebuild-lockfiles: .uv + uv lock --upgrade + cargo update + +.PHONY: build-dev ## Build the development version of the package +build-dev: + uv run maturin build + +.PHONY: build-wheel ## Build production wheel and install it +build-wheel: + @rm -rf target/wheels/ + uv run maturin build --release + @wheel=$$(ls target/wheels/*.whl); uv pip install --force-reinstall "$$wheel[pandas,polars]" + +.PHONY: lint-python ## Lint python source files +lint-python: + uv run ruff check $(sources) $(test_sources) + uv run ruff format --check $(sources) $(test_sources) + uv run mypy $(sources) + +.PHONY: lint-rust ## Lint rust source files +lint-rust: + cargo fmt --all -- --check + cargo clippy --workspace --tests -- -D warnings + +.PHONY: lint ## Lint rust and python source files +lint: lint-python lint-rust + +.PHONY: format-python ## Auto-format python source files +format-python: + uv run ruff check --fix $(sources) $(test_sources) + uv run ruff format $(sources) $(test_sources) + +.PHONY: format-rust ## Auto-format rust source files +format-rust: + cargo fmt --all + cargo clippy --workspace --tests --fix --allow-dirty --allow-staged + +.PHONY: format ## Auto-format python and rust source files +format: format-rust format-python + +.PHONY: test-python ## Run python tests +test-python: install + uv run pytest $(sources) $(test_sources) --doctest-modules --ignore=$(test_sources)/benchmarks + +.PHONY: test-rust ## Run rust tests +test-rust: + cargo test --workspace + +.PHONY: test ## Run all tests +test: test-rust test-python + +.PHONY: benchmarks ## Run benchmarks +benchmarks: .uv + uv sync --frozen --group dev --group benchmarks + uv run maturin develop --uv --release -E pyarrow,pandas,polars + uv run pytest python/tests/benchmarks --benchmark-group-by=group --benchmark-warmup=off --benchmark-max-time=2 + +.PHONY: doc-serve ## Serve documentation with live reload +doc-serve: build-dev + uv run pdoc python/tracking_data_container + +.PHONY: doc ## Build documentation +doc: build-dev + uv run pdoc -o docs python/tracking_data_container + cargo doc --no-deps --workspace + +.PHONY: all ## Run the standard set of checks performed in CI +all: format build-dev lint test + +.PHONY: clean ## Clear local caches and build artifacts +clean: + rm -rf `find . -name __pycache__` + rm -f `find . -type f -name '*.py[co]' ` + rm -f `find . -type f -name '*~' ` + rm -f `find . -type f -name '.*~' ` + rm -rf .cache + rm -rf htmlcov + rm -rf .pytest_cache + rm -rf *.egg-info + rm -f .coverage + rm -f .coverage.* + rm -rf build + rm -rf target + rm -rf perf.data* + rm -rf python/tracking_data_container/*.so + +.PHONY: help ## Display this message +help: + @grep -E \ + '^.PHONY: .*?## .*$$' $(MAKEFILE_LIST) | \ + sort | \ + awk 'BEGIN {FS = ".PHONY: |## "}; {printf "\033[36m%-19s\033[0m %s\n", $$2, $$3}' diff --git a/README.md b/README.md new file mode 100644 index 0000000..47b76c9 --- /dev/null +++ b/README.md @@ -0,0 +1,154 @@ +# Tracking Data Container (TDC) + +[![CI](https://github.com/probberechts/tracking-data-container/actions/workflows/CI.yml/badge.svg)](https://github.com/probberechts/tracking-data-container/actions/workflows/CI.yml) +[![PyPI version](https://img.shields.io/pypi/v/tracking-data-container.svg)](https://pypi.org/project/tracking-data-container/) +[![Crates.io](https://img.shields.io/crates/v/tdc-core.svg)](https://crates.io/crates/tdc-core) + +## Summary + +The **TrackingDataContainer (TDC)** is a shared, standardized, metadata-aware container for sports tracking data. Backed by Apache Arrow, it acts as the central interface between tools like Kloppy, Floodlight, BallRadar, and others. + +The container combines high-performance, zero-copy data access with a rich metadata model, enabling efficient workflows. It provides a primary I/O interface for the sports tracking data ecosystem. + +## Motivation + +As tracking pipelines grow across football and other sports, the need for modularity, performance, and inter-package interoperability is critical. Many open-source packages in football analytics currently duplicate functionality across parsing, modeling, and data management. + +TDC addresses several key challenges: + +- **Scalability**: Object-based models (e.g., Kloppy) don't scale well to large tracking datasets. +- **Interoperability**: Packages often use inconsistent schemas and lack a shared representation. +- **Efficiency**: Development resources are wasted on duplicated data parsing logic. +- **Reproducibility**: Derived metrics and transforms are often not easily shareable. +- **Flexibility**: Support is needed for skeleton data, physical data, and multi-team use cases. + +By leveraging Apache Arrow with enforced metadata and a shared schema, TDC solves these problems while maintaining compatibility with modern analytics engines (Polars, DuckDB, etc.). + +## Key Features + +- **Standardized CDF Schema**: Fully aligned with the [Common Data Format (CDF)](https://doi.org/10.48550/arXiv.2401.01882) standard for interoperability. +- **Zero-Copy Interoperability**: Seamlessly move data between TDC, Polars, Pandas, and PyArrow using the Arrow PyCapsule interface. +- **Lazy Evaluation**: Deferred computation and filter pushdown using Polars and DataFusion. +- **Metadata Awareness**: Native support for coordinate systems, pitch dimensions, and orientation normalization (e.g., attacking direction). +- **Frame Materialization**: Efficiently extract and inspect individual frames indexed by time or index. + +## Python Installation + +```bash +pip install tracking_data_container +``` + +## Python Usage + +### Creating and Inspecting Data + +```python +import tracking_data_container as tdc +from datetime import datetime, timezone + +data = { + "frame_id": [1, 2], + "timestamp": [datetime.now(timezone.utc), datetime.now(timezone.utc)], + "ball/x": [10.0, 11.0], + "ball/y": [20.0, 21.0], + "period": ["first_half", "first_half"] +} +container = tdc.TrackingDataContainer(data) + +# Materialize a specific frame +frame = container.materialize_frame(0) +print(frame["ball/x"]) # 10.0 +``` + +### Derived Metrics and Selectors + +```python +# Add a derived column lazily using selectors +lazy_c = container.lazy() +plan = lazy_c.add_column( + "ball/speed", + (tdc.col("ball/x").diff().fill_null(0).pow(2) + + tdc.col("ball/y").diff().fill_null(0).pow(2)).sqrt() +) +result = plan.collect() + +# Select all home player positions +home_players = result.select(result.selector.players(team_id="home")) +``` + +### Coordinate Transformations + +```python +# Scale to a metric coordinate system +# First, define the current coordinate system +current_sys = tdc.CoordinateSystem( + x_range=(0, 100), + y_range=(0, 100), + origin="top_left", + length_unit="percent" +) +container = container.with_coordinate_system(current_sys) + +# Then transform to the target system +metric_sys = tdc.CoordinateSystem( + x_range=(-52.5, 52.5), + y_range=(-34, 34), + origin="center", + length_unit="meters" +) +metric_container = container.transform_coordinates(metric_sys) +``` + +## Project Structure + +- `crates/tdc-core`: Core Rust implementation (Apache Arrow + domain logic). +- `crates/tdc-python`: PyO3 bindings. +- `python/`: High-level Python package. + +## Development + +TDC is a mixed Rust/Python project. We use `uv` for Python dependency management and `maturin` for building the Rust extensions. + +### Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) (latest stable) +- [uv](https://docs.astral.sh/uv/getting-started/installation/) + +### Setup and Building + +```bash +# First-time setup (installs dependencies and pre-commit hooks) +make setup-dev + +# Build the development version +make build-dev +``` + +### Testing + +```bash +# Run all tests (Rust + Python) +make test + +# Run Rust tests only +make test-rust + +# Run Python tests only +make test-python +``` + +### Linting and Formatting + +We use `ruff` and `mypy` for Python, and `cargo fmt/clippy` for Rust. + +```bash +# Run all linters +make lint + +# Auto-format all code +make format +``` + +## License + +MIT diff --git a/RFC.md b/RFC.md index 4cf272b..4b1559d 100644 --- a/RFC.md +++ b/RFC.md @@ -10,7 +10,7 @@ The container combines high-performance, zero-copy data access with a rich metad ## Motivation -As tracking pipelines grow across football and other sports, the need for modularity, performance, and inter-package interoperability becomes critical. A growing number of open-source packages in football analytics are duplicating functionality across parsing, modeling, and data management. This [comparison spreadsheet](https://docs.google.com/spreadsheets/d/1sfkqxkmtmQkXmkHiuNz-kvBvTLL7UWpCHdLuxW4zAF4/edit?gid=0#gid=0) illustrates overlapping efforts across different research groups and tools. +As tracking pipelines grow across football and other sports, the need for modularity, performance, and inter-package interoperability becomes critical. A growing number of open-source packages in football analytics are duplicating functionality across parsing, modeling, and data management. This [comparison spreadsheet](https://docs.google.com/spreadsheets/d/1sfkqxkmtmQkXmkHiuNz-kvBvTLL7UWpCHdLuxW4zAF4/edit?gid=0#gid=0) illustrates overlapping efforts across different research groups and tools. Current challenges: @@ -99,13 +99,13 @@ We propose to align column naming with the [Common Data Format (CDF)](https://do Example conventions based on CDF: -| Concept | Column Name | -|-----------|---------------------------------------------| -| Ball | `ball_x`, `ball_y`, `ball_z` | -| Player | `teams/players/_x`, `_y` | -| Skeleton | `teams/players//_x` | -| Metrics | `package/metric_name` | -| Time | `timestamp`, `frame_id`, `period` | +| Concept | Column Name | +| -------- | ------------------------------------ | +| Ball | `ball_x`, `ball_y`, `ball_z` | +| Player | `teams/players/_x`, `_y` | +| Skeleton | `teams/players//_x` | +| Metrics | `package/metric_name` | +| Time | `timestamp`, `frame_id`, `period` | These conventions follow: @@ -124,16 +124,334 @@ By adhering to CDF, the `TrackingDataContainer` will be interoperable with other - Defines optional logical "layers" (e.g. tracking, skeleton, predictions) - Use kloppy Metadata objects -### Storage & Computation Model +--- + +## Storage Layer Specification + +### Schema Design + +#### Semi-Long Format + +TrackingDataContainer uses a **semi-long format**: one row per entity (player/ball) per timestamp, with measurements as separate columns. + +```sql +CREATE TABLE tracking_data ( + -- Identifiers + match_id STRING, -- Partition key + timestamp TIMESTAMP(us), -- Microsecond precision + frame_id BIGINT, -- Optional frame counter + period_id TINYINT, -- 1, 2, etc. + + -- Entity identification + player_id STRING, -- CV ID or stable player ID (dictionary encoded) + team_id STRING, -- "home" / "away" / null (dictionary encoded) + + -- Measurements + x FLOAT, + y FLOAT, + z FLOAT -- Nullable (height) +) PARTITIONED BY (match_id, bucket(10, player_id)); +``` + +**Rationale:** + +- **Semi-long format**: Scales with variable player counts, handles ID instability (10K+ IDs from CV systems) +- **String IDs with dictionary encoding**: Efficient storage, flexible for unstable/non-numeric IDs +- **Separate x,y,z columns**: Enables SIMD vectorization for per-player operations +- **Bucketing**: Distributes high-cardinality player_id across fixed number of files + +### Storage Architecture + +#### Apache Iceberg Table Format + +**Primary storage**: Apache Iceberg tables with Parquet file format + +``` +warehouse/ +├── match_metadata/ # Separate metadata table +│ ├── metadata/ +│ └── data/ +└── tracking_data/ # Main tracking data table + ├── metadata/ + └── data/ + ├── match_id=123/ + │ ├── player_id_bucket=0/*.parquet + │ ├── player_id_bucket=1/*.parquet + │ └── ... + └── match_id=124/ + └── ... +``` + +**Why Iceberg:** + +- ✅ Stores optimization settings in metadata (bucket_count, sort_order) +- ✅ Schema evolution built-in +- ✅ Time-travel / versioning +- ✅ Can swap file format (Parquet → Lance/Vortex) via maintenance jobs +- ✅ Mature ecosystem (DuckDB, Spark, Trino support) + +**Catalog**: Start with filesystem catalog, design supports REST/Glue/Hive catalogs + +### Two-Table Design + +#### Table 1: match_metadata (Metadata Storage) + +```sql +CREATE TABLE match_metadata ( + match_id STRING PRIMARY KEY, + + -- Flexible JSON columns for Kloppy metadata + teams JSON, -- {home: {...}, away: {...}} + players JSON, -- [{id, name, jersey_number}, ...] + pitch JSON, -- {length, width, coordinate_system} + periods JSON, -- [{id, start, end}, ...] + + -- Optional metadata + competition_id STRING, + match_date DATE, + venue STRING, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +**Storage**: Non-partitioned table (small, ~1 row per match) + +#### Table 2: tracking_data (Tracking Data) -The container is designed to support **lazy evaluation** and **filter pushdown** using Apache Arrow-compatible formats like Parquet and Iceberg. +```sql +CREATE TABLE tracking_data ( + match_id STRING, + timestamp TIMESTAMP, + player_id STRING, + team_id STRING, + x FLOAT, y FLOAT, z FLOAT + -- Additional columns added via .add_column() +) PARTITIONED BY (match_id, bucket(10, player_id)); +``` + +**Storage**: Partitioned by match, bucketed by player + +### Partitioning Strategy + +#### Per-Match Use Case (Initial) + +```sql +PARTITIONED BY ( + match_id, -- One partition per match + bucket(10, player_id) -- 10 buckets per match +) +``` + +**Configurable bucket count:** + +```python +container.save('warehouse/tracking_data', bucket_count=10) +``` + +**Partition Layout:** + +``` +match_id=123/ + player_id_bucket=0/data.parquet (~10% of players, sorted by player_id) + player_id_bucket=1/data.parquet + ... + player_id_bucket=9/data.parquet +``` -- **Lazy Evaluation**: All transformations (e.g. metric calculations, filters) are applied lazily using Polars' `LazyFrame` or Arrow compute APIs. -- **Filter Pushdown**: When reading from Parquet or Iceberg, the container supports pushing column and row filters down to the storage engine, dramatically improving performance for large datasets. -- **Column Projection**: Only the necessary columns are loaded or materialized, based on selectors or expressions. -- **Iceberg Integration (Future Direction)**: Iceberg can serve as the underlying storage format, offering versioning, schema evolution, and partition pruning. The container will provide adapters to read from and write to Iceberg tables. +**Future use cases** (different partitioning schemes): + +- Competition-level: `PARTITIONED BY (competition_id, match_id, bucket(player_id))` +- Player-centric: `PARTITIONED BY (player_id, match_id)` + +### Optimization Settings + +#### Parquet File Layout + +**Within each partition file:** + +1. **Sorting**: `ORDER BY player_id ASC, timestamp ASC` + - Groups each player's data contiguously + - Enables page-level pruning + +2. **Row Groups**: 128 MB (default) + - Balance between parallelism and metadata overhead + +3. **Page Size**: 1 MB + - **Critical optimization**: Aligns with per-player data size (~750 KB compressed) + - One player ≈ one page → perfect page-level isolation + - Page index enables skipping irrelevant players + +4. **Page Index**: Enabled + - Stores min/max per page + - Essential for sorted data performance + +5. **Compression**: Configurable (default: `snappy`) + - `snappy`: Fast compression/decompression + - `zstd`: Better ratio, slightly slower + +6. **Dictionary Encoding**: Enabled for `player_id`, `team_id` + - Efficient for repeated string values + +**Example configuration:** + +```python +container.save('warehouse/tracking_data', + bucket_count=10, + compression='snappy', + page_size=1024*1024, # 1 MB (advanced) +) +``` -This design ensures the container can be scaled from local processing to lakehouse-style infrastructure with minimal friction. +### Write Strategy + +#### Partition-Level Replacement + +When saving a match, the entire partition is replaced: + +```python +def save(self, path: str, match_id: str): + """Save container (replaces entire match partition).""" + + # 1. Upsert metadata (atomic at table level) + MERGE INTO match_metadata + USING new_metadata + ON match_metadata.match_id = new_metadata.match_id + WHEN MATCHED THEN UPDATE SET * + WHEN NOT MATCHED THEN INSERT *; + + # 2. Replace tracking data partition (atomic at partition level) + DELETE FROM tracking_data WHERE match_id = '123'; + INSERT INTO tracking_data SELECT * FROM new_data; +``` + +**Benefits:** + +- ✅ Idempotent (can re-run safely) +- ✅ Partition replacement is atomic in Iceberg (manifest update) +- ✅ Can rewrite entire match with corrections/new columns + +**Implementation:** DuckDB or PyIceberg (to be validated) + +### Query Performance + +#### Multi-Level Pruning + +Iceberg + optimized Parquet enables efficient queries: + +```python +# Query: Get player 7's data for match 123 +container = TrackingDataContainer.load( + 'warehouse/tracking_data', + match_id='123', + player_id='7' +) +``` + +**Execution plan:** + +1. **Partition pruning**: Only read `match_id=123` partition (skip all other matches) +2. **Bucket pruning**: Hash player_id → read only bucket 7 (skip 90% of files) +3. **Row group pruning**: Check row group stats, skip groups without player 7 +4. **Page-level pruning**: Check page index, read only pages containing player 7 +5. **Result**: ~1 MB read (out of 100+ MB total for match) + +**Pruning efficiency: ~99%** 🎯 + +### Backend Flexibility + +#### Design for Swappable File Formats + +```python +# Current (Parquet) +container.save('warehouse/tracking_data', backend='parquet') + +# Future (via Iceberg maintenance job) +# ALTER TABLE tracking_data SET TBLPROPERTIES ('write.format.default' = 'lance'); +container.save('warehouse/tracking_data', backend='lance') +``` + +**Iceberg allows changing file format without rewriting old data.** + +Potential future formats: + +- **Lance**: Optimized for random access, versioning +- **Vortex**: Columnar format with advanced compression +- **ORC**: Alternative columnar format + +**Skeletal data**: When needed, can store in separate table with different format optimized for high cardinality (22 players × 15 joints = 330 entities). + +### Known Limitations + +#### 1. Cross-Table Atomicity + +Metadata and tracking data are written to separate tables without transactional guarantees. If tracking data write fails after metadata is written, manual cleanup may be required. + +**Future improvements:** + +- Implement two-phase commit pattern +- Add application-level transaction log +- Consider storing metadata within tracking_data table for single-table atomicity + +#### 2. DuckDB Write Limitations + +DuckDB (as of v1.4) cannot UPDATE/DELETE partitioned Iceberg tables directly. Workarounds: + +- Use PyIceberg for writes +- Use DuckDB for reads (excellent query performance) +- Future: DuckDB may add partitioned write support + +### Storage API + +#### User-Facing API + +```python +# Save (TDC handles all optimization) +container.save('warehouse/tracking_data', match_id='123') + +# Load (TDC handles efficient querying) +container = TrackingDataContainer.load( + 'warehouse/tracking_data', + match_id='123', # Optional filters + player_id='7' +) + +# Advanced: Configure optimization +container.save('warehouse/tracking_data', + match_id='123', + bucket_count=50, # More granular partitioning + compression='zstd' # Better compression ratio +) +``` + +**Design principle**: Simple by default, configurable for advanced users. + +### References + +**Iceberg + DuckDB:** + +- [Writes in DuckDB-Iceberg – DuckDB](https://duckdb.org/2025/11/28/iceberg-writes-in-duckdb) +- [Apache Iceberg Writes with DuckDB](https://www.confessionsofadataguy.com/apache-iceberg-writes-with-duckdb-or-not/) +- [Iceberg Extension – DuckDB](https://duckdb.org/docs/stable/core_extensions/iceberg/overview) +- [Transaction Management – DuckDB](https://duckdb.org/docs/stable/sql/statements/transactions) + +**Iceberg Metadata & Partitioning:** + +- [Extending Apache Iceberg: Best Practices for Custom Metadata | Dremio](https://www.dremio.com/blog/extending-apache-iceberg-best-practices-for-storing-and-discovering-custom-metadata/) +- [Understanding Apache Iceberg's metadata.json File](https://dev.to/alexmercedcoder/understanding-apache-icebergs-metadatajson-file-23f) +- [Partitioning Apache Iceberg Tables | Medium](https://medium.com/@tglawless/partitioning-apache-iceberg-tables-ad2757cb0b76) +- [Iceberg Partitioning - Apache Iceberg](https://iceberg.apache.org/docs/latest/partitioning/) + +**Parquet Optimization:** + +- [Cloudera: Speeding Up Queries with Parquet Page Indexes](https://www.cloudera.com/blog/technical/speeding-up-select-queries-with-parquet-page-indexes.html) +- [DataFusion: Parquet Pruning](https://datafusion.apache.org/blog/2025/03/20/parquet-pruning/) +- [Parquet Performance Tuning – Medium](https://medium.com/data-engineering-with-dremio/all-about-parquet-part-10-performance-tuning-and-best-practices-with-parquet-d697ba4e8a57) + +**Cache Locality & Performance:** + +- [Spatial Locality - Algorithmica](https://en.algorithmica.org/hpc/external-memory/locality/) +- [Cache Locality Performance](https://arpitbhayani.me/blogs/cpu-cache-locality) +- [Polars Performance Blog](https://pola.rs/posts/i-wrote-one-of-the-fastest-dataframe-libraries/) --- @@ -150,14 +468,17 @@ This design ensures the container can be scaled from local processing to lakehou ## Rationale & Alternatives ### Why not just Arrow? + - Arrow is fast and standard, but lacks semantics - Metadata enables safe, interpretable, and sport-agnostic usage ### Why enforce the container? + - To guarantee consistency across packages - To avoid schema drift and partial implementations ### Why CDF and flat schema? + - CDF provides a well-vetted, community-driven naming standard - Flat schema increases compatibility and simplifies filter pushdown and projection - Structs may be supported later as an internal representation @@ -192,7 +513,8 @@ This design ensures the container can be scaled from local processing to lakehou - Related tickets: - [Refactor serializer options into own component](https://github.com/PySport/kloppy/issues/10) -> Describes a FrameBuilder approach - [Refactor tracking data model](https://github.com/PySport/kloppy/pull/377) -- Should TDC support transformations? Probably yes, so a package can ensure all data is in the correct orientation and dimensions. +- Should TDC support transformations? Probably yes, so a package can ensure all data is in the correct orientation and dimensions. + --- ## Conclusion @@ -206,6 +528,7 @@ We propose making this the default container for all tools in the ecosystem. We brought together contributors from different open source projects to discuss how we can align our work and improve interoperability in football analytics. Participants included: + - 🇧🇪 Pieter Robberechts (soccerdata, socceraction, Kloppy) - 🇳🇱 Alexander Oonk (databallpy) - 🇯🇵 Keisuke Fujii, Calvin Yeung (OpenSTARLab) diff --git a/crates/tdc-core/Cargo.toml b/crates/tdc-core/Cargo.toml new file mode 100644 index 0000000..b3740a7 --- /dev/null +++ b/crates/tdc-core/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tdc-core" +version = "0.1.0-alpha.1" +edition = "2024" +license = "MIT" +description = "Core engine for standardized sports tracking data containers." + +[dependencies] +arrow = { version = "54" } +arrow-array = { version = "54", features = ["ffi"] } +arrow-schema = { version = "54" } +arrow-select = { version = "54" } +arrow-arith = { version = "54" } +arrow-cast = { version = "54" } +arrow-ord = { version = "54" } +datafusion = "45" +chrono = { version = "^0.4.42", default-features = false } +log = "^0.4" +strum = "0.27.2" +strum_macros = "0.27.2" +regex = "1" +tokio = { version = "1", features = ["rt-multi-thread"] } +rayon = "1.10" +serde = { version = "1.0", features = ["derive"] } +ahash = "0.8" +parking_lot = "0.12" +num_cpus = "1.16" +once_cell = "1.19" +parquet = "54" +serde_json = "1.0.149" + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "ops" +harness = false + +[features] +default = [] diff --git a/crates/tdc-core/benches/ops.rs b/crates/tdc-core/benches/ops.rs new file mode 100644 index 0000000..fd83150 --- /dev/null +++ b/crates/tdc-core/benches/ops.rs @@ -0,0 +1,151 @@ +use arrow_array::Float64Array; +use arrow_schema::DataType; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use datafusion::error::Result as DFResult; +use datafusion::logical_expr::{ColumnarValue, ScalarUDF, ScalarUDFImpl, Signature, Volatility}; +use datafusion::prelude::*; +use std::sync::Arc; +use tdc_core::{ + CoordinateSystem, Direction, LengthUnit, OrientationMode, Origin, TrackingDataBuilder, +}; + +#[derive(Debug)] +struct SpeedUDF { + signature: Signature, +} + +impl SpeedUDF { + fn new() -> Self { + Self { + signature: Signature::exact( + vec![DataType::Float64, DataType::Float64], + Volatility::Immutable, + ), + } + } +} + +impl ScalarUDFImpl for SpeedUDF { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn name(&self) -> &str { + "speed" + } + fn signature(&self) -> &Signature { + &self.signature + } + fn return_type(&self, _arg_types: &[DataType]) -> DFResult { + Ok(DataType::Float64) + } + fn invoke(&self, args: &[ColumnarValue]) -> DFResult { + let args = ColumnarValue::values_to_arrays(args)?; + let vx = args[0].as_any().downcast_ref::().unwrap(); + let vy = args[1].as_any().downcast_ref::().unwrap(); + + let res: Float64Array = vx + .iter() + .zip(vy.iter()) + .map(|(x, y)| match (x, y) { + (Some(x), Some(y)) => Some((x * x + y * y).sqrt()), + _ => None, + }) + .collect(); + + Ok(ColumnarValue::Array(Arc::new(res))) + } +} + +fn setup_container(num_rows: usize) -> tdc_core::TrackingDataContainer { + let mut builder = TrackingDataBuilder::new(); + let vx_idx = builder.get_or_create_column_id("vx", DataType::Float64); + let vy_idx = builder.get_or_create_column_id("vy", DataType::Float64); + let ball_x_idx = builder.get_or_create_column_id("ball/x", DataType::Float64); + let ball_y_idx = builder.get_or_create_column_id("ball/y", DataType::Float64); + let period_idx = builder.get_or_create_column_id("period", DataType::Utf8); + + for i in 0..num_rows { + let frame_id = i as i64; + let row_idx = builder.get_row_index(frame_id); + builder.insert_string_at_idx( + row_idx, + period_idx, + if i < num_rows / 2 { + "first_half" + } else { + "second_half" + }, + ); + builder.insert_float_at_idx(row_idx, ball_x_idx, i as f64 * 0.1); + builder.insert_float_at_idx(row_idx, ball_y_idx, i as f64 * 0.05); + builder.insert_float_at_idx(row_idx, vx_idx, 1.5); + builder.insert_float_at_idx(row_idx, vy_idx, 2.0); + } + + let container = builder.build().unwrap(); + let source_sys = CoordinateSystem { + x_range: Some((-52.5, 52.5)), + y_range: Some((-34.0, 34.0)), + origin: Origin::Center, + x_direction: Direction::Right, + y_direction: Direction::Up, + length_unit: Some(LengthUnit::Meters), + }; + container + .with_coordinate_system(source_sys) + .with_orientation(OrientationMode::StaticHomeAway) +} + +fn bench_ops(c: &mut Criterion) { + let num_rows = 100_000; + let container = setup_container(num_rows); + let ctx = SessionContext::new(); + let rt = tokio::runtime::Runtime::new().unwrap(); + + // 1. Transform Coordinates + let target_sys = CoordinateSystem { + x_range: Some((0.0, 100.0)), + y_range: Some((0.0, 100.0)), + origin: Origin::TopLeft, + x_direction: Direction::Right, + y_direction: Direction::Down, + length_unit: Some(LengthUnit::Percent), + }; + + c.bench_function("transform_coordinates", |b| { + b.iter(|| { + black_box(&container) + .transform_coordinates(black_box(target_sys.clone())) + .unwrap() + }) + }); + + // 2. Transform Orientation + c.bench_function("transform_orientation", |b| { + b.iter(|| { + black_box(&container) + .transform_orientation(black_box(OrientationMode::HomeAway)) + .unwrap() + }) + }); + + // 3. UDF (Speed) + let speed_udf = ScalarUDF::from(SpeedUDF::new()); + let expr = speed_udf.call(vec![col("vx"), col("vy")]); + + c.bench_function("add_speed_column_udf", |b| { + b.iter(|| { + black_box(&container) + .with_expr_columns_ctx( + vec!["speed".to_string()], + vec![black_box(expr.clone())], + &ctx, + rt.handle(), + ) + .unwrap() + }) + }); +} + +criterion_group!(benches, bench_ops); +criterion_main!(benches); diff --git a/crates/tdc-core/src/config.rs b/crates/tdc-core/src/config.rs new file mode 100644 index 0000000..c6f788a --- /dev/null +++ b/crates/tdc-core/src/config.rs @@ -0,0 +1,27 @@ +use datafusion::prelude::{SessionConfig, SessionContext}; +use once_cell::sync::Lazy; +use tokio::runtime::Runtime; + +pub static RUNTIME: Lazy = Lazy::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to create Tokio runtime") +}); + +pub static CONTEXT: Lazy = Lazy::new(|| { + let config = SessionConfig::new() + .with_target_partitions(num_cpus::get()) + .with_batch_size(16384) + .with_repartition_joins(true) + .with_repartition_aggregations(true) + .with_repartition_windows(true) + .with_repartition_sorts(true) + .with_parquet_pruning(true); + + SessionContext::new_with_config(config) +}); + +pub fn num_cpus() -> usize { + num_cpus::get() +} diff --git a/crates/tdc-core/src/datacontainer/builder.rs b/crates/tdc-core/src/datacontainer/builder.rs new file mode 100644 index 0000000..2c1bc12 --- /dev/null +++ b/crates/tdc-core/src/datacontainer/builder.rs @@ -0,0 +1,1382 @@ +use crate::error::TrackingDataContainerResult; +use crate::schema::{DataField, Fields, FrameAttribute}; +use ahash::AHashMap; +use arrow::buffer::{BooleanBuffer, MutableBuffer}; +use arrow::util::bit_util; +use arrow_array::{Array, ArrayRef, BooleanArray, Int64Array, RecordBatch, StringArray}; +use arrow_schema::{DataType, Field, Schema}; +use std::sync::Arc; + +/// A simplified builder for a single column that handles random-access insertion. +#[derive(Debug)] +struct TypedColumn { + values: Vec, + validity: MutableBuffer, // Bit-packed validity map + null_value: T, +} + +impl Clone for TypedColumn { + fn clone(&self) -> Self { + let mut validity = MutableBuffer::with_capacity(self.validity.len()); + validity.extend_from_slice(self.validity.as_slice()); + Self { + values: self.values.clone(), + validity, + null_value: self.null_value, + } + } +} + +impl TypedColumn { + fn new(capacity: usize, null_value: T) -> Self { + let mut validity = MutableBuffer::new(capacity.div_ceil(8)); + validity.extend_zeros(capacity.div_ceil(8)); + Self { + values: vec![null_value; capacity], + validity, + null_value, + } + } + + #[inline] + fn len(&self) -> usize { + self.values.len() + } + + #[inline] + fn ensure_capacity(&mut self, target_len: usize) { + if target_len > self.len() { + self.values.resize(target_len, self.null_value); + let new_validity_bytes = target_len.div_ceil(8); + if new_validity_bytes > self.validity.len() { + self.validity + .extend_zeros(new_validity_bytes - self.validity.len()); + } + } + } + + #[inline] + fn insert(&mut self, index: usize, val: T) { + if index >= self.len() { + self.ensure_capacity(index + 1); + } + self.values[index] = val; + bit_util::set_bit(self.validity.as_slice_mut(), index); + } + + fn rebase(&mut self, mapping: &[(usize, usize)], new_total: usize) { + let mut new_values = vec![self.null_value; new_total]; + let mut new_validity = MutableBuffer::new(new_total.div_ceil(8)); + new_validity.extend_zeros(new_total.div_ceil(8)); + + for &(old_idx, new_idx) in mapping { + if old_idx < self.values.len() && bit_util::get_bit(self.validity.as_slice(), old_idx) { + new_values[new_idx] = self.values[old_idx]; + bit_util::set_bit(new_validity.as_slice_mut(), new_idx); + } + } + self.values = new_values; + self.validity = new_validity; + } + + fn merge_fast(&mut self, other: &Self, min_row: usize, max_row: usize) { + self.ensure_capacity(other.len()); + if max_row < min_row || min_row >= other.len() { + return; + } + let end = (max_row + 1).min(other.len()); + + let src_values = &other.values[min_row..end]; + let dst_values = &mut self.values[min_row..end]; + + for i in 0..src_values.len() { + let row = min_row + i; + if bit_util::get_bit(other.validity.as_slice(), row) { + dst_values[i] = src_values[i]; + bit_util::set_bit(self.validity.as_slice_mut(), row); + } + } + } + + fn merge_contiguous(&mut self, other: Self) { + let old_len = self.len(); + let other_len = other.len(); + self.values.extend(other.values); + + let new_total_len = old_len + other_len; + let new_validity_bytes = new_total_len.div_ceil(8); + self.validity.resize(new_validity_bytes, 0); + + for i in 0..other_len { + if bit_util::get_bit(other.validity.as_slice(), i) { + bit_util::set_bit(self.validity.as_slice_mut(), old_len + i); + } + } + } + + fn merge_from(&mut self, other: &Self, mapping: &[(usize, usize)], new_total: usize) { + self.ensure_capacity(new_total); + for &(other_idx, self_idx) in mapping { + if other_idx < other.len() && bit_util::get_bit(other.validity.as_slice(), other_idx) { + self.values[self_idx] = other.values[other_idx]; + bit_util::set_bit(self.validity.as_slice_mut(), self_idx); + } + } + } +} + +#[derive(Debug)] +enum ColumnBuilder { + Int(TypedColumn), + UInt8(TypedColumn), + UInt32(TypedColumn), + Timestamp(TypedColumn), + Float32(TypedColumn), + Float64(TypedColumn), + Bool { + values: MutableBuffer, + validity: MutableBuffer, + len: usize, + }, + String { + values: Vec>, + }, + Dictionary { + values: TypedColumn, + dictionary: Vec, + lookup: AHashMap, + }, +} + +impl Clone for ColumnBuilder { + fn clone(&self) -> Self { + match self { + Self::Int(c) => Self::Int(c.clone()), + Self::UInt8(c) => Self::UInt8(c.clone()), + Self::UInt32(c) => Self::UInt32(c.clone()), + Self::Timestamp(c) => Self::Timestamp(c.clone()), + Self::Float32(c) => Self::Float32(c.clone()), + Self::Float64(c) => Self::Float64(c.clone()), + Self::Bool { + values, + validity, + len, + } => { + let mut v = MutableBuffer::with_capacity(values.len()); + v.extend_from_slice(values.as_slice()); + let mut val = MutableBuffer::with_capacity(validity.len()); + val.extend_from_slice(validity.as_slice()); + Self::Bool { + values: v, + validity: val, + len: *len, + } + } + Self::String { values } => Self::String { + values: values.clone(), + }, + Self::Dictionary { + values, + dictionary, + lookup, + } => Self::Dictionary { + values: values.clone(), + dictionary: dictionary.clone(), + lookup: lookup.clone(), + }, + } + } +} + +impl ColumnBuilder { + fn new(capacity: usize, dtype: DataType) -> Self { + match dtype { + DataType::Int64 | DataType::UInt64 => Self::Int(TypedColumn::new(capacity, 0)), + DataType::UInt8 => Self::UInt8(TypedColumn::new(capacity, 0)), + DataType::UInt32 => Self::UInt32(TypedColumn::new(capacity, 0)), + DataType::Timestamp(_, _) => Self::Timestamp(TypedColumn::new(capacity, 0)), + DataType::Float32 => Self::Float32(TypedColumn::new(capacity, f32::NAN)), + DataType::Float64 => Self::Float64(TypedColumn::new(capacity, f64::NAN)), + DataType::Boolean => { + let bytes = capacity.div_ceil(8); + let mut values = MutableBuffer::new(bytes); + values.extend_zeros(bytes); + let mut validity = MutableBuffer::new(bytes); + validity.extend_zeros(bytes); + Self::Bool { + values, + validity, + len: capacity, + } + } + DataType::Dictionary(_, _) => Self::Dictionary { + values: TypedColumn::new(capacity, 0), + dictionary: Vec::new(), + lookup: AHashMap::new(), + }, + _ => Self::String { + values: vec![None; capacity], + }, + } + } + + fn ensure_capacity(&mut self, target_len: usize) { + match self { + Self::Int(c) => c.ensure_capacity(target_len), + Self::UInt8(c) => c.ensure_capacity(target_len), + Self::UInt32(c) => c.ensure_capacity(target_len), + Self::Timestamp(c) => c.ensure_capacity(target_len), + Self::Float32(c) => c.ensure_capacity(target_len), + Self::Float64(c) => c.ensure_capacity(target_len), + Self::Bool { + values, + validity, + len, + } => { + if target_len > *len { + let bytes = target_len.div_ceil(8); + if bytes > values.len() { + values.extend_zeros(bytes - values.len()); + validity.extend_zeros(bytes - validity.len()); + } + *len = target_len; + } + } + Self::String { values } => values.resize(target_len, None), + Self::Dictionary { values, .. } => values.ensure_capacity(target_len), + } + } + + #[inline] + fn insert_int(&mut self, index: usize, val: i64) { + match self { + Self::Int(c) => c.insert(index, val), + Self::UInt8(c) => c.insert(index, val as u8), + Self::UInt32(c) => c.insert(index, val as u32), + Self::Timestamp(c) => c.insert(index, val), + Self::Float32(_) | Self::Float64(_) => { + self.insert_float(index, val as f64); + } + Self::Dictionary { .. } => self.insert_string(index, &val.to_string()), + _ => { + self.convert_to_string(); + if let Self::String { values } = self { + if index >= values.len() { + values.resize(index + 1, None); + } + values[index] = Some(val.to_string()); + } + } + } + } + + #[inline] + fn insert_float(&mut self, index: usize, val: f64) { + match self { + Self::Float32(c) => c.insert(index, val as f32), + Self::Float64(c) => c.insert(index, val), + Self::Int(_) => { + self.convert_to_float64(); + self.insert_float(index, val); + } + Self::Dictionary { .. } => self.insert_string(index, &val.to_string()), + _ => { + self.convert_to_string(); + if let Self::String { values } = self { + if index >= values.len() { + values.resize(index + 1, None); + } + values[index] = Some(val.to_string()); + } + } + } + } + + #[inline] + fn insert_string(&mut self, index: usize, val: &str) { + match self { + Self::String { values } => { + if index >= values.len() { + values.resize(index + 1, None); + } + values[index] = Some(val.to_string()); + } + Self::Dictionary { + values, + dictionary, + lookup, + } => { + let id = if let Some(&id) = lookup.get(val) { + id + } else { + let s = val.to_string(); + let id = dictionary.len() as u32; + dictionary.push(s.clone()); + lookup.insert(s, id); + id + }; + values.insert(index, id); + } + _ => { + self.convert_to_string(); + if let Self::String { values } = self { + if index >= values.len() { + values.resize(index + 1, None); + } + values[index] = Some(val.to_string()); + } + } + } + } + + #[inline] + fn insert_bool(&mut self, index: usize, val: bool) { + match self { + Self::Bool { + values, + validity, + len, + } => { + if index >= *len { + let target_len = index + 1; + let bytes = target_len.div_ceil(8); + if bytes > values.len() { + values.extend_zeros(bytes - values.len()); + validity.extend_zeros(bytes - validity.len()); + } + *len = target_len; + } + if val { + bit_util::set_bit(values.as_slice_mut(), index); + } else { + bit_util::unset_bit(values.as_slice_mut(), index); + } + bit_util::set_bit(validity.as_slice_mut(), index); + } + _ => { + self.convert_to_string(); + if let Self::String { values } = self { + if index >= values.len() { + values.resize(index + 1, None); + } + values[index] = Some(val.to_string()); + } + } + } + } + + fn convert_to_float64(&mut self) { + match self { + Self::Int(c) => { + let mut new_c = TypedColumn::new(c.len(), f64::NAN); + for i in 0..c.len() { + if bit_util::get_bit(c.validity.as_slice(), i) { + new_c.insert(i, c.values[i] as f64); + } + } + *self = Self::Float64(new_c); + } + Self::Float32(c) => { + let mut new_c = TypedColumn::new(c.len(), f64::NAN); + for i in 0..c.len() { + if bit_util::get_bit(c.validity.as_slice(), i) { + new_c.insert(i, c.values[i] as f64); + } + } + *self = Self::Float64(new_c); + } + _ => {} + } + } + + fn convert_to_string(&mut self) { + let values: Vec> = match self { + Self::Int(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::UInt8(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::UInt32(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::Timestamp(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::Float32(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::Float64(c) => (0..c.len()) + .map(|i| { + if bit_util::get_bit(c.validity.as_slice(), i) { + Some(c.values[i].to_string()) + } else { + None + } + }) + .collect(), + Self::Bool { + values, + validity, + len, + } => (0..*len) + .map(|i| { + if bit_util::get_bit(validity.as_slice(), i) { + Some(bit_util::get_bit(values.as_slice(), i).to_string()) + } else { + None + } + }) + .collect(), + Self::Dictionary { + values, dictionary, .. + } => (0..values.len()) + .map(|i| { + if bit_util::get_bit(values.validity.as_slice(), i) { + Some(dictionary[values.values[i] as usize].clone()) + } else { + None + } + }) + .collect(), + Self::String { .. } => return, + }; + *self = Self::String { values }; + } + + fn rebase(&mut self, mapping: &[(usize, usize)], new_total: usize) { + match self { + Self::Int(c) => c.rebase(mapping, new_total), + Self::UInt8(c) => c.rebase(mapping, new_total), + Self::UInt32(c) => c.rebase(mapping, new_total), + Self::Timestamp(c) => c.rebase(mapping, new_total), + Self::Float32(c) => c.rebase(mapping, new_total), + Self::Float64(c) => c.rebase(mapping, new_total), + Self::Bool { + values, + validity, + len, + } => { + let bytes = new_total.div_ceil(8); + let mut new_v = MutableBuffer::new(bytes); + new_v.extend_zeros(bytes); + let mut new_val = MutableBuffer::new(bytes); + new_val.extend_zeros(bytes); + + for &(old_idx, new_idx) in mapping { + if old_idx < *len && bit_util::get_bit(validity.as_slice(), old_idx) { + if bit_util::get_bit(values.as_slice(), old_idx) { + bit_util::set_bit(new_v.as_slice_mut(), new_idx); + } + bit_util::set_bit(new_val.as_slice_mut(), new_idx); + } + } + *values = new_v; + *validity = new_val; + *len = new_total; + } + Self::String { values } => { + let mut new_v = vec![None; new_total]; + for &(old_idx, new_idx) in mapping { + if old_idx < values.len() { + new_v[new_idx] = values[old_idx].take(); + } + } + *values = new_v; + } + Self::Dictionary { values, .. } => { + values.rebase(mapping, new_total); + } + } + } + + fn merge_fast(&mut self, other: ColumnBuilder, min_row: usize, max_row: usize) { + match (self, other) { + (Self::Int(s), ColumnBuilder::Int(o)) => s.merge_fast(&o, min_row, max_row), + (Self::UInt8(s), ColumnBuilder::UInt8(o)) => s.merge_fast(&o, min_row, max_row), + (Self::UInt32(s), ColumnBuilder::UInt32(o)) => s.merge_fast(&o, min_row, max_row), + (Self::Timestamp(s), ColumnBuilder::Timestamp(o)) => s.merge_fast(&o, min_row, max_row), + (Self::Float32(s), ColumnBuilder::Float32(o)) => s.merge_fast(&o, min_row, max_row), + (Self::Float64(s), ColumnBuilder::Float64(o)) => s.merge_fast(&o, min_row, max_row), + ( + Self::Bool { + values: s_v, + validity: s_val, + len: s_l, + }, + ColumnBuilder::Bool { + values: o_v, + validity: o_val, + len: o_l, + }, + ) => { + if o_l > *s_l { + let target_len = o_l; + let bytes = target_len.div_ceil(8); + if bytes > s_v.len() { + s_v.extend_zeros(bytes - s_v.len()); + s_val.extend_zeros(bytes - s_val.len()); + } + *s_l = target_len; + } + let end = (max_row + 1).min(o_l); + for i in min_row..end { + if bit_util::get_bit(o_val.as_slice(), i) { + if bit_util::get_bit(o_v.as_slice(), i) { + bit_util::set_bit(s_v.as_slice_mut(), i); + } else { + bit_util::unset_bit(s_v.as_slice_mut(), i); + } + bit_util::set_bit(s_val.as_slice_mut(), i); + } + } + } + (Self::String { values: s_v }, ColumnBuilder::String { values: o_v }) => { + if o_v.len() > s_v.len() { + s_v.resize(o_v.len(), None); + } + let end = (max_row + 1).min(o_v.len()); + for i in min_row..end { + if o_v[i].is_some() { + s_v[i] = o_v[i].clone(); + } + } + } + ( + Self::Dictionary { + values: s_v, + dictionary: s_d, + lookup: s_l, + }, + ColumnBuilder::Dictionary { + values: o_v, + dictionary: o_d, + lookup: _o_l, + }, + ) => { + s_v.ensure_capacity(o_v.len()); + let end = (max_row + 1).min(o_v.len()); + for i in min_row..end { + if bit_util::get_bit(o_v.validity.as_slice(), i) { + let val = &o_d[o_v.values[i] as usize]; + let new_id = *s_l.entry(val.clone()).or_insert_with(|| { + let id = s_d.len() as u32; + s_d.push(val.clone()); + id + }); + s_v.insert(i, new_id); + } + } + } + (s, o) => { + s.convert_to_string(); + let mut o = o; + o.convert_to_string(); + if let (Self::String { values: s_v }, ColumnBuilder::String { values: o_v }) = + (s, o) + { + if o_v.len() > s_v.len() { + s_v.resize(o_v.len(), None); + } + let end = (max_row + 1).min(o_v.len()); + for i in min_row..end { + if o_v[i].is_some() { + s_v[i] = o_v[i].clone(); + } + } + } + } + } + } + + fn merge_contiguous(&mut self, other: ColumnBuilder) { + match (self, other) { + (Self::Int(s), ColumnBuilder::Int(o)) => s.merge_contiguous(o), + (Self::UInt8(s), ColumnBuilder::UInt8(o)) => s.merge_contiguous(o), + (Self::UInt32(s), ColumnBuilder::UInt32(o)) => s.merge_contiguous(o), + (Self::Timestamp(s), ColumnBuilder::Timestamp(o)) => s.merge_contiguous(o), + (Self::Float32(s), ColumnBuilder::Float32(o)) => s.merge_contiguous(o), + (Self::Float64(s), ColumnBuilder::Float64(o)) => s.merge_contiguous(o), + ( + Self::Bool { + values: s_v, + validity: s_val, + len: s_l, + }, + ColumnBuilder::Bool { + values: o_v, + validity: o_val, + len: o_l, + }, + ) => { + let old_len = *s_l; + let other_len = o_l; + let target_len = old_len + other_len; + let bytes = target_len.div_ceil(8); + if bytes > s_v.len() { + s_v.extend_zeros(bytes - s_v.len()); + s_val.extend_zeros(bytes - s_val.len()); + } + *s_l = target_len; + + for i in 0..other_len { + if bit_util::get_bit(o_val.as_slice(), i) { + if bit_util::get_bit(o_v.as_slice(), i) { + bit_util::set_bit(s_v.as_slice_mut(), old_len + i); + } + bit_util::set_bit(s_val.as_slice_mut(), old_len + i); + } + } + } + (Self::String { values: s_v }, ColumnBuilder::String { values: o_v }) => { + s_v.extend(o_v) + } + ( + Self::Dictionary { + values: s_v, + dictionary: s_d, + lookup: s_l, + }, + ColumnBuilder::Dictionary { + values: o_v, + dictionary: o_d, + lookup: _o_l, + }, + ) => { + let old_len = s_v.len(); + let other_len = o_v.len(); + s_v.ensure_capacity(old_len + other_len); + + for i in 0..other_len { + if bit_util::get_bit(o_v.validity.as_slice(), i) { + let val = &o_d[o_v.values[i] as usize]; + let new_id = *s_l.entry(val.clone()).or_insert_with(|| { + let id = s_d.len() as u32; + s_d.push(val.clone()); + id + }); + s_v.insert(old_len + i, new_id); + } + } + } + (s, o) => { + s.convert_to_string(); + let mut o = o; + o.convert_to_string(); + if let (Self::String { values: s_v }, ColumnBuilder::String { values: o_v }) = + (s, o) + { + s_v.extend(o_v); + } + } + } + } + + fn merge_from(&mut self, other: ColumnBuilder, mapping: &[(usize, usize)], new_total: usize) { + match (self, other) { + (Self::Int(s), ColumnBuilder::Int(o)) => s.merge_from(&o, mapping, new_total), + (Self::UInt8(s), ColumnBuilder::UInt8(o)) => s.merge_from(&o, mapping, new_total), + (Self::UInt32(s), ColumnBuilder::UInt32(o)) => s.merge_from(&o, mapping, new_total), + (Self::Timestamp(s), ColumnBuilder::Timestamp(o)) => { + s.merge_from(&o, mapping, new_total) + } + (Self::Float32(s), ColumnBuilder::Float32(o)) => s.merge_from(&o, mapping, new_total), + (Self::Float64(s), ColumnBuilder::Float64(o)) => s.merge_from(&o, mapping, new_total), + ( + Self::Bool { + values: s_v, + validity: s_val, + len: s_l, + }, + ColumnBuilder::Bool { + values: o_v, + validity: o_val, + len: o_l, + }, + ) => { + if new_total > *s_l { + let target_len = new_total; + let bytes = target_len.div_ceil(8); + if bytes > s_v.len() { + s_v.extend_zeros(bytes - s_v.len()); + s_val.extend_zeros(bytes - s_val.len()); + } + *s_l = target_len; + } + for &(o_idx, s_idx) in mapping { + if o_idx < o_l && bit_util::get_bit(o_val.as_slice(), o_idx) { + if bit_util::get_bit(o_v.as_slice(), o_idx) { + bit_util::set_bit(s_v.as_slice_mut(), s_idx); + } else { + bit_util::unset_bit(s_v.as_slice_mut(), s_idx); + } + bit_util::set_bit(s_val.as_slice_mut(), s_idx); + } + } + } + ( + Self::Dictionary { + values: s_v, + dictionary: s_d, + lookup: s_l, + }, + ColumnBuilder::Dictionary { + values: o_v, + dictionary: o_d, + .. + }, + ) => { + s_v.ensure_capacity(new_total); + for &(o_idx, s_idx) in mapping { + if o_idx < o_v.len() && bit_util::get_bit(o_v.validity.as_slice(), o_idx) { + let val = &o_d[o_v.values[o_idx] as usize]; + let new_id = *s_l.entry(val.clone()).or_insert_with(|| { + let nid = s_d.len() as u32; + s_d.push(val.clone()); + nid + }); + s_v.insert(s_idx, new_id); + } + } + } + (s, o) => { + s.convert_to_string(); + let mut o = o; + o.convert_to_string(); + if let (Self::String { values: s_v }, ColumnBuilder::String { values: o_v }) = + (s, o) + { + s_v.resize(new_total, None); + for &(other_idx, self_idx) in mapping { + if other_idx < o_v.len() + && let Some(v) = &o_v[other_idx] + { + s_v[self_idx] = Some(v.clone()); + } + } + } + } + } + } + + fn finish(self) -> (ArrayRef, DataType) { + match self { + Self::Int(c) => { + let len = c.len(); + ( + Arc::new(Int64Array::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::Int64, + ) + } + Self::UInt8(c) => { + use arrow_array::UInt8Array; + let len = c.len(); + ( + Arc::new(UInt8Array::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::UInt8, + ) + } + Self::UInt32(c) => { + use arrow_array::UInt32Array; + let len = c.len(); + ( + Arc::new(UInt32Array::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::UInt32, + ) + } + Self::Timestamp(c) => { + use arrow_array::PrimitiveArray; + use arrow_array::types::TimestampMicrosecondType; + let len = c.len(); + ( + Arc::new(PrimitiveArray::::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None), + ) + } + Self::Float32(c) => { + use arrow_array::Float32Array; + let len = c.len(); + ( + Arc::new(Float32Array::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::Float32, + ) + } + Self::Float64(c) => { + use arrow_array::Float64Array; + let len = c.len(); + ( + Arc::new(Float64Array::new( + c.values.into(), + Some(BooleanBuffer::new(c.validity.into(), 0, len).into()), + )), + DataType::Float64, + ) + } + Self::Bool { + values, + validity, + len, + } => ( + Arc::new(BooleanArray::new( + BooleanBuffer::new(values.into(), 0, len), + Some(BooleanBuffer::new(validity.into(), 0, len).into()), + )), + DataType::Boolean, + ), + Self::Dictionary { + values, dictionary, .. + } => { + use arrow_array::{DictionaryArray, UInt32Array}; + let len = values.len(); + let keys = UInt32Array::new( + values.values.into(), + Some(BooleanBuffer::new(values.validity.into(), 0, len).into()), + ); + let values = StringArray::from(dictionary); + let array = DictionaryArray::new(keys, Arc::new(values)); + let dtype = array.data_type().clone(); + (Arc::new(array), dtype) + } + Self::String { values } => (Arc::new(StringArray::from(values)), DataType::Utf8), + } + } + + fn convert_to_match(&mut self, other: &Self) { + match (self, other) { + (Self::Int(_), ColumnBuilder::Int(_)) => (), + (Self::UInt8(_), ColumnBuilder::UInt8(_)) => (), + (Self::UInt32(_), ColumnBuilder::UInt32(_)) => (), + (Self::Timestamp(_), ColumnBuilder::Timestamp(_)) => (), + (Self::Float32(_), ColumnBuilder::Float32(_)) => (), + (Self::Float64(_), ColumnBuilder::Float64(_)) => (), + (Self::Bool { .. }, ColumnBuilder::Bool { .. }) => (), + (Self::String { .. }, ColumnBuilder::String { .. }) => (), + (Self::Dictionary { .. }, ColumnBuilder::Dictionary { .. }) => (), + (s, _) => s.convert_to_string(), + } + } +} + +pub struct TrackingDataBuilder { + frame_map: AHashMap, + last_frame: Option<(i64, usize)>, + columns: Vec, + column_names: Vec, + column_index: AHashMap, + domain_fields: Vec, + next_row_index: usize, + frame_offset: i64, + is_fixed: bool, + min_row_touched: usize, + max_row_touched: usize, +} + +impl Default for TrackingDataBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TrackingDataBuilder { + pub fn new() -> Self { + Self { + frame_map: AHashMap::with_capacity(1000), + last_frame: None, + columns: Vec::with_capacity(100), + column_names: Vec::with_capacity(100), + column_index: AHashMap::with_capacity(100), + domain_fields: Vec::with_capacity(100), + next_row_index: 0, + frame_offset: 0, + is_fixed: false, + min_row_touched: usize::MAX, + max_row_touched: 0, + } + } + + pub fn new_fixed(min_id: i64, max_id: i64) -> Self { + let capacity = (max_id - min_id + 1) as usize; + Self { + frame_map: AHashMap::new(), + last_frame: None, + columns: Vec::with_capacity(100), + column_names: Vec::with_capacity(100), + column_index: AHashMap::with_capacity(100), + domain_fields: Vec::with_capacity(100), + next_row_index: capacity, + frame_offset: min_id, + is_fixed: true, + min_row_touched: usize::MAX, + max_row_touched: 0, + } + } + + #[inline] + pub fn get_row_index(&mut self, frame_id: i64) -> usize { + let idx = if self.is_fixed { + (frame_id - self.frame_offset) as usize + } else { + if let Some((last_id, last_idx)) = self.last_frame + && last_id == frame_id + { + return last_idx; + } + let next_idx = self.next_row_index; + let idx = *self.frame_map.entry(frame_id).or_insert_with(|| { + self.next_row_index += 1; + next_idx + }); + self.last_frame = Some((frame_id, idx)); + idx + }; + if idx < self.min_row_touched { + self.min_row_touched = idx; + } + if idx > self.max_row_touched { + self.max_row_touched = idx; + } + idx + } + + pub fn get_or_create_column_id(&mut self, name: &str, mut dtype: DataType) -> usize { + if let Some(&idx) = self.column_index.get(name) { + return idx; + } + let field = DataField::from_name(name); + if field.is_low_cardinality() && dtype == DataType::Utf8 { + dtype = DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8)); + } + let idx = self.columns.len(); + self.column_index.insert(name.to_string(), idx); + self.column_names.push(name.to_string()); + self.columns + .push(ColumnBuilder::new(self.next_row_index, dtype)); + self.domain_fields.push(field); + idx + } + + #[inline] + fn update_touched_bounds(&mut self, row_idx: usize) { + if row_idx < self.min_row_touched { + self.min_row_touched = row_idx; + } + if row_idx > self.max_row_touched { + self.max_row_touched = row_idx; + } + } + + #[inline] + pub fn insert_int_at_idx(&mut self, row_idx: usize, col_idx: usize, val: i64) { + self.update_touched_bounds(row_idx); + self.columns[col_idx].insert_int(row_idx, val); + } + #[inline] + pub fn insert_float_at_idx(&mut self, row_idx: usize, col_idx: usize, val: f64) { + self.update_touched_bounds(row_idx); + self.columns[col_idx].insert_float(row_idx, val); + } + #[inline] + pub fn insert_string_at_idx(&mut self, row_idx: usize, col_idx: usize, val: &str) { + self.update_touched_bounds(row_idx); + self.columns[col_idx].insert_string(row_idx, val); + } + #[inline] + pub fn insert_bool_at_idx(&mut self, row_idx: usize, col_idx: usize, val: bool) { + self.update_touched_bounds(row_idx); + self.columns[col_idx].insert_bool(row_idx, val); + } + + #[inline] + pub fn insert_int(&mut self, frame_id: i64, col: &str, val: i64) { + let r = self.get_row_index(frame_id); + let d = DataField::from_name(col).default_arrow_type(); + let c = self.get_or_create_column_id(col, d); + self.columns[c].insert_int(r, val); + } + #[inline] + pub fn insert_float(&mut self, frame_id: i64, col: &str, val: f64) { + let r = self.get_row_index(frame_id); + let d = DataField::from_name(col).default_arrow_type(); + let c = self.get_or_create_column_id(col, d); + self.columns[c].insert_float(r, val); + } + #[inline] + pub fn insert_string(&mut self, frame_id: i64, col: &str, val: &str) { + let r = self.get_row_index(frame_id); + let d = DataField::from_name(col).default_arrow_type(); + let c = self.get_or_create_column_id(col, d); + self.columns[c].insert_string(r, val); + } + #[inline] + pub fn insert_bool(&mut self, frame_id: i64, col: &str, val: bool) { + let r = self.get_row_index(frame_id); + let d = DataField::from_name(col).default_arrow_type(); + let c = self.get_or_create_column_id(col, d); + self.columns[c].insert_bool(r, val); + } + + pub fn merge(&mut self, other: TrackingDataBuilder) { + let mut other = other; + if other.column_index.is_empty() { + return; + } + + let other_min = other.min_row_touched; + let other_max = other.max_row_touched; + + // Contiguous merge (StatsPerform) + if self.is_fixed + && other.is_fixed + && self.frame_offset + self.next_row_index as i64 == other.frame_offset + { + for (name, other_idx) in other.column_index { + let other_col = std::mem::replace( + &mut other.columns[other_idx], + ColumnBuilder::Int(TypedColumn::new(0, 0)), + ); + let other_domain_field = std::mem::replace( + &mut other.domain_fields[other_idx], + DataField::from_name("dummy"), + ); + if let Some(&self_idx) = self.column_index.get(&name) { + self.columns[self_idx].merge_contiguous(other_col); + } else { + let mut new_col = ColumnBuilder::new(self.next_row_index, DataType::Int64); + new_col.convert_to_match(&other_col); + new_col.merge_contiguous(other_col); + self.column_index.insert(name.clone(), self.columns.len()); + self.column_names.push(name.clone()); + self.columns.push(new_col); + self.domain_fields.push(other_domain_field); + } + } + self.next_row_index += other.next_row_index; + if other_min <= other_max { + let mapped_max = other_max + self.next_row_index - other.next_row_index; + if mapped_max > self.max_row_touched { + self.max_row_touched = mapped_max; + } + } + return; + } + + // Fast path (Sportec) + if self.is_fixed + && other.is_fixed + && self.frame_offset == other.frame_offset + && self.next_row_index == other.next_row_index + { + for (name, other_idx) in other.column_index { + let other_col = std::mem::replace( + &mut other.columns[other_idx], + ColumnBuilder::Int(TypedColumn::new(0, 0)), + ); + let other_domain_field = std::mem::replace( + &mut other.domain_fields[other_idx], + DataField::from_name("dummy"), + ); + if let Some(&self_idx) = self.column_index.get(&name) { + if other_min <= other_max { + self.columns[self_idx].merge_fast(other_col, other_min, other_max); + } + } else { + self.column_index.insert(name.clone(), self.columns.len()); + self.column_names.push(name.clone()); + self.columns.push(other_col); + self.domain_fields.push(other_domain_field); + } + } + if other_min <= other_max { + if other_min < self.min_row_touched { + self.min_row_touched = other_min; + } + if other_max > self.max_row_touched { + self.max_row_touched = other_max; + } + } + return; + } + + // --- Middle path: Sorted, non-overlapping frame streams --- + // If both builders have sorted frame_ids and they don't overlap, we can do a very fast + // O(N) merge by simply rebasing both. + // We check this by comparing min/max frame IDs. + let (self_min, self_max) = if self.is_fixed { + ( + self.frame_offset, + self.frame_offset + self.next_row_index as i64 - 1, + ) + } else { + // Check if frame_map is sorted. For simplicity, we only optimize if is_fixed + // or if we can easily determine it's sorted. + (0, -1) // Disable for now if not fixed + }; + + let (other_min, other_max_id) = if other.is_fixed { + ( + other.frame_offset, + other.frame_offset + other.next_row_index as i64 - 1, + ) + } else { + (0, -1) + }; + + if self_min <= self_max + && other_min <= other_max_id + && (self_max < other_min || other_max_id < self_min) + { + let ( + first_builder_offset, + first_builder_len, + second_builder_offset, + second_builder_len, + swap, + ) = if self_max < other_min { + ( + self.frame_offset, + self.next_row_index, + other.frame_offset, + other.next_row_index, + false, + ) + } else { + ( + other.frame_offset, + other.next_row_index, + self.frame_offset, + self.next_row_index, + true, + ) + }; + + let gap = (second_builder_offset - (first_builder_offset + first_builder_len as i64)) + as usize; + let new_total = first_builder_len + gap + second_builder_len; + let new_offset = first_builder_offset; + + if !swap { + // 'self' is first. Just resize and then merge 'other'. + for col in &mut self.columns { + col.rebase( + &(0..self.next_row_index).map(|i| (i, i)).collect::>(), + new_total, + ); + } + let other_mapping: Vec<(usize, usize)> = (0..other.next_row_index) + .map(|i| (i, i + first_builder_len + gap)) + .collect(); + + for (name, other_idx) in other.column_index { + let other_col = std::mem::replace( + &mut other.columns[other_idx], + ColumnBuilder::Int(TypedColumn::new(0, 0)), + ); + let other_domain_field = std::mem::replace( + &mut other.domain_fields[other_idx], + DataField::from_name("dummy"), + ); + if let Some(&self_idx) = self.column_index.get(&name) { + self.columns[self_idx].merge_from(other_col, &other_mapping, new_total); + } else { + let mut new_col = other_col; + new_col.rebase(&other_mapping, new_total); + self.column_index.insert(name.clone(), self.columns.len()); + self.column_names.push(name.clone()); + self.columns.push(new_col); + self.domain_fields.push(other_domain_field); + } + } + self.next_row_index = new_total; + self.frame_offset = new_offset; + self.is_fixed = true; + self.max_row_touched = new_total - 1; + return; + } else { + // 'other' is first. This is more complex because we need to move 'self' data. + // Fall back to slow path for now if swap is needed, or implement it if common. + } + } + + // Slow path (Fallback) + let mut all_frame_ids: Vec = if self.is_fixed { + (0..self.next_row_index) + .map(|i| self.frame_offset + i as i64) + .collect() + } else { + self.frame_map.keys().cloned().collect() + }; + + if other.is_fixed { + for i in 0..other.next_row_index { + all_frame_ids.push(other.frame_offset + i as i64); + } + } else { + for id in other.frame_map.keys() { + all_frame_ids.push(*id); + } + } + + all_frame_ids.sort_unstable(); + all_frame_ids.dedup(); + + let new_total = all_frame_ids.len(); + let mut new_frame_map = AHashMap::with_capacity(new_total); + for (idx, id) in all_frame_ids.iter().enumerate() { + new_frame_map.insert(*id, idx); + } + + let self_mapping: Vec<(usize, usize)> = if self.is_fixed { + (0..self.next_row_index) + .map(|i| { + ( + i, + *new_frame_map.get(&(self.frame_offset + i as i64)).unwrap(), + ) + }) + .collect() + } else { + self.frame_map + .iter() + .map(|(id, old)| (*old, *new_frame_map.get(id).unwrap())) + .collect() + }; + for col in &mut self.columns { + col.rebase(&self_mapping, new_total); + } + + let other_mapping: Vec<(usize, usize)> = if other.is_fixed { + (0..other.next_row_index) + .map(|i| { + ( + i, + *new_frame_map.get(&(other.frame_offset + i as i64)).unwrap(), + ) + }) + .collect() + } else { + other + .frame_map + .iter() + .map(|(id, old)| (*old, *new_frame_map.get(id).unwrap())) + .collect() + }; + + for (name, other_idx) in other.column_index { + let other_col = std::mem::replace( + &mut other.columns[other_idx], + ColumnBuilder::Int(TypedColumn::new(0, 0)), + ); + let other_domain_field = std::mem::replace( + &mut other.domain_fields[other_idx], + DataField::from_name("dummy"), + ); + if let Some(&self_idx) = self.column_index.get(&name) { + self.columns[self_idx].merge_from(other_col, &other_mapping, new_total); + } else { + let mut new_col = other_col; + new_col.rebase(&other_mapping, new_total); + self.column_index.insert(name.clone(), self.columns.len()); + self.column_names.push(name.clone()); + self.columns.push(new_col); + self.domain_fields.push(other_domain_field); + } + } + self.frame_map = new_frame_map; + self.next_row_index = new_total; + self.last_frame = None; + self.min_row_touched = 0; + self.max_row_touched = new_total.saturating_sub(1); + self.is_fixed = false; + } + + pub fn build(self) -> TrackingDataContainerResult { + let total_rows = self.next_row_index; + let mut fields = Vec::with_capacity(self.columns.len() + 1); + let mut arrays = Vec::with_capacity(self.columns.len() + 1); + let mut domain_fields = Vec::with_capacity(self.columns.len() + 1); + + for ((name, mut builder), domain_field) in self + .column_names + .into_iter() + .zip(self.columns) + .zip(self.domain_fields) + { + builder.ensure_capacity(total_rows); + let (array, dtype) = builder.finish(); + fields.push(Field::new(name, dtype, true)); + arrays.push(array); + domain_fields.push(domain_field); + } + + if !self.column_index.contains_key("frame_id") { + let mut frame_id_builder = vec![0i64; total_rows]; + if self.is_fixed { + for (i, val) in frame_id_builder.iter_mut().enumerate().take(total_rows) { + *val = self.frame_offset + i as i64; + } + } else { + for (id, idx) in self.frame_map { + frame_id_builder[idx] = id; + } + } + fields.push(Field::new("frame_id", DataType::Int64, false)); + arrays.push(Arc::new(Int64Array::from(frame_id_builder)) as ArrayRef); + domain_fields.push(DataField::Frame { + attribute: FrameAttribute::FrameId, + }); + } + + let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), arrays)?; + Ok(super::TrackingDataContainer::new( + batch.into(), + Fields { + fields: domain_fields, + }, + )) + } +} diff --git a/crates/tdc-core/src/datacontainer/core.rs b/crates/tdc-core/src/datacontainer/core.rs new file mode 100644 index 0000000..91f7a9a --- /dev/null +++ b/crates/tdc-core/src/datacontainer/core.rs @@ -0,0 +1,877 @@ +use crate::error::{TrackingDataContainerErrorKind, TrackingDataContainerResult}; +use crate::schema::{ + CoordinateSystem, DataField, Fields, OrientationMode, SchemaId, TrackedEntity, +}; +use arrow_arith::numeric::{add, mul}; +use arrow_array::array::{Array, ArrayRef, Float32Array, Float64Array, StructArray}; +use arrow_array::cast::AsArray; +use arrow_array::{RecordBatch, Scalar}; +use arrow_ord::cmp::eq; +use arrow_schema::{DataType, DataType::*, Field, Fields as ArrowFields}; +use arrow_select::zip::zip; +use datafusion::prelude::*; +use std::sync::Arc; + +#[derive(Debug)] +pub struct TrackingDataContainer { + pub data: StructArray, + pub schema: Fields, + pub coordinate_system: Option, + pub orientation: Option, + pub schema_id: Option, +} + +impl Clone for TrackingDataContainer { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + } + } +} + +impl TrackingDataContainer { + pub fn new(data: StructArray, schema: Fields) -> Self { + Self { + data, + schema, + coordinate_system: None, + orientation: None, + schema_id: None, + } + } + + pub fn with_coordinate_system(mut self, system: CoordinateSystem) -> Self { + self.coordinate_system = Some(system); + self + } + + pub fn with_orientation(mut self, mode: OrientationMode) -> Self { + self.orientation = Some(mode); + self + } + + pub fn with_schema_id(mut self, id: SchemaId) -> Self { + self.schema_id = Some(id); + self + } + + pub fn num_rows(&self) -> usize { + self.data.len() + } + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn slice(&self, offset: usize, length: usize) -> Self { + Self { + data: self.data.slice(offset, length), + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + } + } + + pub fn validate(&self) -> TrackingDataContainerResult<()> { + match &self.schema_id { + Some(id) => self.schema.validate_with_schema(id)?, + None => self.schema.validate()?, + } + + let default_id = crate::schema::registry::default_schema_id(); + let schema_id = self.schema_id.as_ref().unwrap_or(&default_id); + + if let Some(schema_def) = crate::schema::registry::get_schema(schema_id) { + for req in &schema_def.value_requirements { + if let Some(idx) = self + .schema + .fields + .iter() + .position(|f| f.name() == req.column) + { + let col = self.data.column(idx); + if let Some(string_col) = col.as_any().downcast_ref::() { + for i in 0..string_col.len() { + if !string_col.is_null(i) { + let val = string_col.value(i); + if !req.allowed_values.iter().any(|v| v == val) { + return Err(crate::error::TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaError(format!( + "Invalid value for column '{}': {}", + req.column, val + )), + )); + } + } + } + } else if let Some(large_string_col) = col.as_any().downcast_ref::() { + for i in 0..large_string_col.len() { + if !large_string_col.is_null(i) { + let val = large_string_col.value(i); + if !req.allowed_values.iter().any(|v| v == val) { + return Err(crate::error::TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaError(format!( + "Invalid value for column '{}': {}", + req.column, val + )), + )); + } + } + } + } else if let Some(dict_col) = col.as_any().downcast_ref::>() { + let values = dict_col.values(); + if let Some(string_values) = values.as_any().downcast_ref::() { + for i in 0..dict_col.len() { + if !dict_col.is_null(i) { + let key = dict_col.keys().value(i); + let val = string_values.value(key as usize); + if !req.allowed_values.iter().any(|v| v == val) { + return Err(crate::error::TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaError(format!( + "Invalid value for column '{}': {}", + req.column, val + )), + )); + } + } + } + } else if let Some(large_string_values) = values.as_any().downcast_ref::() { + for i in 0..dict_col.len() { + if !dict_col.is_null(i) { + let key = dict_col.keys().value(i); + let val = large_string_values.value(key as usize); + if !req.allowed_values.iter().any(|v| v == val) { + return Err(crate::error::TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaError(format!( + "Invalid value for column '{}': {}", + req.column, val + )), + )); + } + } + } + } + } + } + } + } + Ok(()) + } + + pub fn lazy(&self) -> TrackingDataContainerResult { + let batch = RecordBatch::from(self.data.clone()); + let df = crate::config::CONTEXT.read_batch(batch)?; + Ok(super::LazyTrackingDataContainer::new( + df, + self.schema.clone(), + self.coordinate_system.clone(), + self.orientation, + self.schema_id.clone(), + )) + } + + pub fn with_columns(&self, fields: Vec, arrays: Vec) -> Self { + let mut new_fields: Vec> = self.data.fields().iter().cloned().collect(); + let mut new_columns: Vec = self.data.columns().to_vec(); + let mut new_schema_fields = self.schema.fields.clone(); + let mut field_map: std::collections::HashMap = new_fields + .iter() + .enumerate() + .map(|(i, f): (usize, &Arc)| (f.name().clone(), i)) + .collect(); + + for (field, array) in fields.into_iter().zip(arrays) { + let name = field.name(); + if let Some(&i) = field_map.get(&name) { + new_columns[i] = array.clone(); + new_fields[i] = Arc::new(Field::new(&name, array.data_type().clone(), true)); + new_schema_fields[i] = field; + } else { + let new_idx = new_fields.len(); + new_fields.push(Arc::new(Field::new(&name, array.data_type().clone(), true))); + new_columns.push(array); + new_schema_fields.push(field); + field_map.insert(name, new_idx); + } + } + Self { + data: StructArray::new(ArrowFields::from(new_fields), new_columns, None), + schema: Fields { + fields: new_schema_fields, + }, + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + } + } + + pub fn with_expr_columns_ctx( + &self, + names: Vec, + exprs: Vec, + ctx: &SessionContext, + rt: &tokio::runtime::Handle, + ) -> TrackingDataContainerResult { + let mut df = ctx.read_batch(RecordBatch::from(self.data.clone()))?; + let new_names = names.clone(); + for (name, expr) in names.into_iter().zip(exprs) { + df = df.with_column(&name, expr)?; + } + df = df.select_columns(&new_names.iter().map(|s| s.as_str()).collect::>())?; + + let batches = rt.block_on(async { df.collect().await })?; + if batches.is_empty() { + return Ok(self.clone()); + } + let full_batch = arrow_select::concat::concat_batches(&batches[0].schema(), &batches)?; + let fields: Vec = full_batch + .schema() + .fields() + .iter() + .map(|f| DataField::from_name(f.name())) + .collect(); + let mut columns = Vec::new(); + for i in 0..full_batch.num_columns() { + columns.push(full_batch.column(i).clone()); + } + Ok(self.with_columns(fields, columns)) + } + + pub fn transform_coordinates( + &self, + to_system: CoordinateSystem, + ) -> TrackingDataContainerResult { + let from = self.coordinate_system.as_ref().ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters("No coordinate system".to_string()) + })?; + + let mut new_cols = self.data.columns().to_vec(); + let mut new_fields = self.data.fields().iter().cloned().collect::>(); + + let (m_x, o_x) = from.get_transform_coeffs(&to_system, true)?; + let (m_y, o_y) = from.get_transform_coeffs(&to_system, false)?; + let (m_z, o_z) = (m_y, o_y); + + let m_x_f64 = Scalar::new(Float64Array::from(vec![m_x])); + let o_x_f64 = Scalar::new(Float64Array::from(vec![o_x])); + let m_y_f64 = Scalar::new(Float64Array::from(vec![m_y])); + let o_y_f64 = Scalar::new(Float64Array::from(vec![o_y])); + let m_z_f64 = Scalar::new(Float64Array::from(vec![m_z])); + let o_z_f64 = Scalar::new(Float64Array::from(vec![o_z])); + + let m_x_f32 = Scalar::new(Float32Array::from(vec![m_x as f32])); + let o_x_f32 = Scalar::new(Float32Array::from(vec![o_x as f32])); + let m_y_f32 = Scalar::new(Float32Array::from(vec![m_y as f32])); + let o_y_f32 = Scalar::new(Float32Array::from(vec![o_y as f32])); + let m_z_f32 = Scalar::new(Float32Array::from(vec![m_z as f32])); + let o_z_f32 = Scalar::new(Float32Array::from(vec![o_z as f32])); + + for (i, field) in self.schema.fields.iter().enumerate() { + if let DataField::TrackedEntity { attribute, .. } = field { + use crate::schema::TrackedEntityAttribute::*; + if matches!(attribute, X | Y | Z) { + let (m_f64, o_f64, m_f32, o_f32) = match attribute { + X => (&m_x_f64, &o_x_f64, &m_x_f32, &o_x_f32), + Y => (&m_y_f64, &o_y_f64, &m_y_f32, &o_y_f32), + Z => (&m_z_f64, &o_z_f64, &m_z_f32, &o_z_f32), + _ => unreachable!(), + }; + + let col = self.data.column(i); + let transformed: ArrayRef = match col.data_type() { + Float32 => { + let multiplied = mul(col, m_f32)?; + add(&multiplied, o_f32)? + } + Float64 => { + let multiplied = mul(col, m_f64)?; + add(&multiplied, o_f64)? + } + _ => { + let arr = arrow_cast::cast(col, &Float64)?; + let multiplied = mul(&arr, m_f64)?; + add(&multiplied, o_f64)? + } + }; + new_cols[i] = transformed.clone(); + new_fields[i] = Arc::new(Field::new( + new_fields[i].name(), + transformed.data_type().clone(), + true, + )); + } + } + } + let new_data = StructArray::try_new( + ArrowFields::from(new_fields), + new_cols, + self.data.nulls().cloned(), + )?; + Ok(Self { + data: new_data, + schema: self.schema.clone(), + coordinate_system: Some(to_system), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub fn transform_orientation( + &self, + mode: OrientationMode, + ) -> TrackingDataContainerResult { + let current = self.orientation.as_ref().ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters("No orientation".to_string()) + })?; + if current == &mode { + return Ok(self.clone()); + } + let p_idx = self + .schema + .fields + .iter() + .position(|f| { + matches!( + f, + DataField::Frame { + attribute: crate::schema::FrameAttribute::Period + } + ) + }) + .ok_or_else(|| { + TrackingDataContainerErrorKind::SchemaError("Missing period column".to_string()) + })?; + let p_col = self.data.column(p_idx); + + let mut new_cols = self.data.columns().to_vec(); + let new_fields = self.data.fields().iter().cloned().collect::>(); + + let poss_team_idx = self.schema.fields.iter().position(|f| { + matches!( + f, + DataField::Frame { + attribute: crate::schema::FrameAttribute::BallPossTeamId + } + ) + }); + + let current_mask = self.get_flip_mask(current, p_col, poss_team_idx); + let target_mask = self.get_flip_mask(&mode, p_col, poss_team_idx); + let change_mask = arrow_arith::boolean::or( + &arrow_arith::boolean::and_not(¤t_mask, &target_mask)?, + &arrow_arith::boolean::and_not(&target_mask, ¤t_mask)?, + )?; + + let (m_x, o_x) = self + .coordinate_system + .as_ref() + .map(|cs: &CoordinateSystem| cs.get_flip_coeffs(true)) + .unwrap_or((-1.0, 0.0)); + let (m_y, o_y) = self + .coordinate_system + .as_ref() + .map(|cs: &CoordinateSystem| cs.get_flip_coeffs(false)) + .unwrap_or((-1.0, 0.0)); + + let m_x_f64 = Scalar::new(Float64Array::from(vec![m_x])); + let o_x_f64 = Scalar::new(Float64Array::from(vec![o_x])); + let m_y_f64 = Scalar::new(Float64Array::from(vec![m_y])); + let o_y_f64 = Scalar::new(Float64Array::from(vec![o_y])); + + let m_x_f32 = Scalar::new(Float32Array::from(vec![m_x as f32])); + let o_x_f32 = Scalar::new(Float32Array::from(vec![o_x as f32])); + let m_y_f32 = Scalar::new(Float32Array::from(vec![m_y as f32])); + let o_y_f32 = Scalar::new(Float32Array::from(vec![o_y as f32])); + + for (i, field) in self.schema.fields.iter().enumerate() { + if let DataField::TrackedEntity { attribute, .. } = field + && matches!( + attribute, + crate::schema::TrackedEntityAttribute::X + | crate::schema::TrackedEntityAttribute::Y + ) + { + let is_x = matches!(attribute, crate::schema::TrackedEntityAttribute::X); + let (m_f64, o_f64, m_f32, o_f32) = if is_x { + (&m_x_f64, &o_x_f64, &m_x_f32, &o_x_f32) + } else { + (&m_y_f64, &o_y_f64, &m_y_f32, &o_y_f32) + }; + + let col = self.data.column(i); + let transformed: ArrayRef = match col.data_type() { + Float32 => { + let multiplied = mul(col, m_f32)?; + let flipped = add(&multiplied, o_f32)?; + zip(&change_mask, &flipped, col)? + } + Float64 => { + let multiplied = mul(col, m_f64)?; + let flipped = add(&multiplied, o_f64)?; + zip(&change_mask, &flipped, col)? + } + _ => col.clone(), + }; + new_cols[i] = transformed; + } + } + let new_data = StructArray::try_new( + ArrowFields::from(new_fields), + new_cols, + self.data.nulls().cloned(), + )?; + Ok(Self { + data: new_data, + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: Some(mode), + schema_id: self.schema_id.clone(), + }) + } + + fn get_flip_mask( + &self, + mode: &OrientationMode, + p_col: &ArrayRef, + poss_team_idx: Option, + ) -> arrow_array::BooleanArray { + let num_rows = self.num_rows(); + match mode { + OrientationMode::HomeAway => eq( + p_col, + &Scalar::new(arrow_array::StringArray::from(vec!["second_half"])), + ) + .unwrap(), + OrientationMode::AwayHome => eq( + p_col, + &Scalar::new(arrow_array::StringArray::from(vec!["first_half"])), + ) + .unwrap(), + OrientationMode::BallOwningTeam => { + if let Some(idx) = poss_team_idx { + let poss_col = self.data.column(idx); + eq( + poss_col, + &Scalar::new(arrow_array::StringArray::from(vec!["away"])), + ) + .unwrap() + } else { + arrow_array::BooleanArray::new_null(num_rows) + } + } + _ => arrow_array::BooleanArray::new_null(num_rows), + } + } + + pub fn pivot( + data: StructArray, + entity_to_team: Option>, + ) -> TrackingDataContainerResult { + use crate::datacontainer::builder::TrackingDataBuilder; + use ahash::AHashSet; + use arrow_array::types::{ + Float32Type, Float64Type, Int64Type, TimestampMicrosecondType, UInt64Type, + }; + use std::str::FromStr; + + if data.is_empty() { + return Ok(Self::new(data, Fields { fields: vec![] })); + } + + let fields = data.fields(); + let columns = data.columns(); + + // 1. Identify key columns (Strictly using snake_case names) + let mut frame_id_idx = None; + let mut timestamp_idx = None; + let mut entity_id_idx = None; + let mut team_id_idx = None; + let mut attr_indices = Vec::new(); + let mut core_indices = Vec::new(); + + let core_names: AHashSet<&str> = [ + "match_id", + "competition_id", + "season_id", + "timestamp", + "frame_id", + "period", + "period_id", + "ball_status", + "ball_poss_team_id", + "ball_poss_status", + ] + .into_iter() + .collect(); + + for (i, field) in fields.iter().enumerate() { + let name = field.name().as_str(); + if name == "frame_id" { + frame_id_idx = Some(i); + core_indices.push(i); + } else if name == "timestamp" { + timestamp_idx = Some(i); + core_indices.push(i); + } else if name == "entity_id" || name == "player/id" || name == "player_id" { + entity_id_idx = Some(i); + attr_indices.push(i); // Also pivot it + } else if name == "team_id" || name == "team/id" { + team_id_idx = Some(i); + attr_indices.push(i); // Also pivot it + } else if core_names.contains(name) { + core_indices.push(i); + } else if name == "entity_type" || name == "entity_key" { + continue; + } else { + attr_indices.push(i); + } + } + + // 2. Select the best frame index column and validate required fields + let has_x = fields.iter().any(|f| f.name() == "x"); + let has_y = fields.iter().any(|f| f.name() == "y"); + let has_period = fields + .iter() + .any(|f| f.name() == "period" || f.name() == "period_id"); + + if !has_x || !has_y { + return Err(TrackingDataContainerErrorKind::InvalidParameters( + "Missing required spatial columns x or y".to_string(), + ) + .into()); + } + if !has_period { + return Err(TrackingDataContainerErrorKind::InvalidParameters( + "Missing required column period or period_id".to_string(), + ) + .into()); + } + + let frame_idx_col = if let Some(fi) = frame_id_idx + && columns[fi].null_count() < columns[fi].len() + { + fi + } else if let Some(ti) = timestamp_idx { + ti + } else { + frame_id_idx.or(timestamp_idx).ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters( + "Missing required frame_id or timestamp column".to_string(), + ) + })? + }; + + let frame_col = &columns[frame_idx_col]; + let is_frame_id = fields[frame_idx_col].name() == "frame_id"; + + let entity_id_idx = entity_id_idx.ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters( + "Missing required entity_id or player/id column".to_string(), + ) + })?; + + // 3. Determine min/max frame index for fixed builder if possible + let mut min_f = i64::MAX; + let mut max_f = i64::MIN; + + let get_frame_val = |i: usize| -> Option { + if frame_col.is_null(i) { + return None; + } + match frame_col.data_type() { + Int64 => Some(frame_col.as_primitive::().value(i)), + UInt64 => Some(frame_col.as_primitive::().value(i) as i64), + Timestamp(_, _) => Some( + frame_col + .as_primitive::() + .value(i), + ), + _ => Some(i as i64), + } + }; + + for i in 0..data.len() { + if let Some(f) = get_frame_val(i) { + if f < min_f { + min_f = f; + } + if f > max_f { + max_f = f; + } + } + } + + let range = if min_f <= max_f { + (max_f - min_f) as usize + } else { + 0 + }; + let mut builder = if is_frame_id && min_f <= max_f && range < 1_000_000 { + TrackingDataBuilder::new_fixed(min_f, max_f) + } else { + TrackingDataBuilder::new() + }; + + // 4. Pre-create columns for core fields + struct CoreColumn { + idx: usize, + builder_idx: usize, + dtype: DataType, + } + let core_cols: Vec = core_indices + .iter() + .map(|&idx| { + let col_name = fields[idx].name(); + let wide_name = match col_name.as_str() { + "match_id" => "match/id", + "competition_id" => "competition/id", + "season_id" => "season/id", + _ => col_name.as_str(), + }; + let dtype = fields[idx].data_type().clone(); + let builder_idx = builder.get_or_create_column_id(wide_name, dtype.clone()); + CoreColumn { + idx, + builder_idx, + dtype, + } + }) + .collect(); + + // 5. Pre-calculate entity attribute mappings + // (player_id, team_id) -> (Vec, Vec) + let mut entity_map = ahash::AHashMap::new(); + + // 6. Pivot data + let mut last_frame_val = None; + let mut last_row_idx = 0; + + for i in 0..data.len() { + let f = match get_frame_val(i) { + Some(val) => val, + None => continue, + }; + + let row_idx = if Some(f) == last_frame_val { + last_row_idx + } else { + let ridx = builder.get_row_index(f); + last_frame_val = Some(f); + last_row_idx = ridx; + ridx + }; + + for c in &core_cols { + let col = &columns[c.idx]; + if !col.is_null(i) { + match &c.dtype { + Int64 => builder.insert_int_at_idx( + row_idx, + c.builder_idx, + col.as_primitive::().value(i), + ), + UInt64 => builder.insert_int_at_idx( + row_idx, + c.builder_idx, + col.as_primitive::().value(i) as i64, + ), + Float32 => builder.insert_float_at_idx( + row_idx, + c.builder_idx, + col.as_primitive::().value(i) as f64, + ), + Float64 => builder.insert_float_at_idx( + row_idx, + c.builder_idx, + col.as_primitive::().value(i), + ), + Utf8 => builder.insert_string_at_idx( + row_idx, + c.builder_idx, + col.as_string::().value(i), + ), + LargeUtf8 => builder.insert_string_at_idx( + row_idx, + c.builder_idx, + col.as_string::().value(i), + ), + Timestamp(_, _) => builder.insert_int_at_idx( + row_idx, + c.builder_idx, + col.as_primitive::().value(i), + ), + _ => {} + } + } + } + + let ent_id_col = &columns[entity_id_idx]; + if ent_id_col.is_null(i) { + continue; + } + let p_id = match ent_id_col.data_type() { + Utf8 => ent_id_col.as_string::().value(i), + LargeUtf8 => ent_id_col.as_string::().value(i), + _ => continue, + }; + + let t_id = if let Some(idx) = team_id_idx { + let col = &columns[idx]; + if col.is_null(i) { + "none" + } else { + match col.data_type() { + Utf8 => col.as_string::().value(i), + LargeUtf8 => col.as_string::().value(i), + _ => "none", + } + } + } else if let Some(ref mapping) = entity_to_team { + mapping.get(p_id).map(|s| s.as_str()).unwrap_or("none") + } else { + "none" + }; + + let attr_mappings = entity_map.entry((p_id, t_id)).or_insert_with(|| { + attr_indices + .iter() + .filter_map(|&attr_idx| { + let attr_name = fields[attr_idx].name(); + let entity = if p_id == "ball" { + TrackedEntity::Ball + } else if let Some(team_id) = p_id.strip_prefix("team:") { + TrackedEntity::Team { + team_id: team_id.to_string(), + } + } else if let Some(referee_id) = p_id.strip_prefix("referee:") { + TrackedEntity::Referee { + referee_id: referee_id.to_string(), + } + } else { + TrackedEntity::Player { + team_id: t_id.to_string(), + player_id: p_id.to_string(), + } + }; + + let attribute = match attr_name.as_str() { + "entity_id" => { + if entity.is_ball() { + return None; + } + crate::schema::TrackedEntityAttribute::Other( + "player/id".to_string(), + ) + } + "team_id" => { + if !entity.is_player() && !entity.is_team() { + return None; + } + crate::schema::TrackedEntityAttribute::Other("team/id".to_string()) + } + "entity_key" => return None, + _ => crate::schema::TrackedEntityAttribute::from_str(attr_name) + .unwrap_or_else(|_| { + crate::schema::TrackedEntityAttribute::Other( + attr_name.to_string(), + ) + }), + }; + + let field = DataField::TrackedEntity { entity, attribute }; + let builder_idx = builder.get_or_create_column_id( + &field.name(), + fields[attr_idx].data_type().clone(), + ); + Some((attr_idx, builder_idx)) + }) + .collect::>() + }); + + for (attr_idx, builder_col_idx) in attr_mappings { + let col: &ArrayRef = &columns[*attr_idx]; + if !col.is_null(i) { + match col.data_type() { + Float32 => builder.insert_float_at_idx( + row_idx, + *builder_col_idx, + col.as_primitive::().value(i) as f64, + ), + Float64 => builder.insert_float_at_idx( + row_idx, + *builder_col_idx, + col.as_primitive::().value(i), + ), + Int64 => builder.insert_int_at_idx( + row_idx, + *builder_col_idx, + col.as_primitive::().value(i), + ), + UInt64 => builder.insert_int_at_idx( + row_idx, + *builder_col_idx, + col.as_primitive::().value(i) as i64, + ), + Utf8 => builder.insert_string_at_idx( + row_idx, + *builder_col_idx, + col.as_string::().value(i), + ), + LargeUtf8 => builder.insert_string_at_idx( + row_idx, + *builder_col_idx, + col.as_string::().value(i), + ), + _ => {} + } + } + } + } + + builder.build() + } +} + +impl crate::selector::Select for TrackingDataContainer { + fn select(&self, fields: Vec) -> TrackingDataContainerResult { + let indices = self.schema.get_column_indices(&fields); + + let mut new_arrays = Vec::with_capacity(fields.len()); + let mut new_arrow_fields = Vec::with_capacity(fields.len()); + let mut new_schema_fields = Vec::with_capacity(fields.len()); + + for (field, maybe_idx) in fields.into_iter().zip(indices) { + if let Some(idx) = maybe_idx { + new_arrays.push(self.data.column(idx).clone()); + new_arrow_fields.push(self.data.fields()[idx].clone()); + new_schema_fields.push(field); + } + } + + let new_data = if new_arrays.is_empty() { + StructArray::new_empty_fields(self.num_rows(), None) + } else { + StructArray::try_new(ArrowFields::from(new_arrow_fields), new_arrays, None)? + }; + + Ok(Self { + data: new_data, + schema: Fields { + fields: new_schema_fields, + }, + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } +} + +impl crate::selector::expr::HasSchema for TrackingDataContainer { + fn schema(&self) -> &Fields { + &self.schema + } +} diff --git a/crates/tdc-core/src/datacontainer/lazy.rs b/crates/tdc-core/src/datacontainer/lazy.rs new file mode 100644 index 0000000..0aeca41 --- /dev/null +++ b/crates/tdc-core/src/datacontainer/lazy.rs @@ -0,0 +1,520 @@ +use crate::datacontainer::TrackingDataContainer; +use crate::error::TrackingDataContainerResult; +use crate::schema::{CoordinateSystem, DataField, Fields, OrientationMode, SchemaId}; +use datafusion::prelude::*; + +#[derive(Clone)] +pub struct LazyTrackingDataContainer { + pub df: DataFrame, + pub schema: Fields, + pub coordinate_system: Option, + pub orientation: Option, + pub schema_id: Option, +} + +impl crate::selector::expr::HasSchema for LazyTrackingDataContainer { + fn schema(&self) -> &Fields { + &self.schema + } +} + +impl crate::selector::Select for LazyTrackingDataContainer { + fn select(&self, fields: Vec) -> TrackingDataContainerResult { + let col_names: Vec = fields.iter().map(|f| f.name()).collect(); + let df = self + .df + .clone() + .select_columns(&col_names.iter().map(|s| s.as_str()).collect::>())?; + + Ok(Self { + df, + schema: Fields { fields }, + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } +} + +impl LazyTrackingDataContainer { + pub fn new( + df: DataFrame, + schema: Fields, + coordinate_system: Option, + orientation: Option, + schema_id: Option, + ) -> Self { + Self { + df, + schema, + coordinate_system, + orientation, + schema_id, + } + } + + pub fn with_orientation(mut self, mode: OrientationMode) -> Self { + self.orientation = Some(mode); + self + } + + pub fn slice(&self, offset: usize, length: usize) -> TrackingDataContainerResult { + let df = self.df.clone().limit(offset, Some(length))?; + Ok(Self { + df, + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub fn filter( + &self, + predicate: datafusion::prelude::Expr, + ) -> TrackingDataContainerResult { + let df = self.df.clone().filter(predicate)?; + Ok(Self { + df, + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub fn transform_coordinates( + &self, + to_system: CoordinateSystem, + ) -> TrackingDataContainerResult { + let from = self.coordinate_system.as_ref().ok_or_else(|| { + crate::error::TrackingDataContainerErrorKind::InvalidParameters( + "No coordinate system".to_string(), + ) + })?; + + let mut current_df = self.df.clone(); + + for field in self.schema.fields.iter() { + if let DataField::TrackedEntity { attribute, .. } = field { + use crate::schema::TrackedEntityAttribute::*; + if matches!(attribute, X | Y | Z) { + let is_x = matches!(attribute, X); + let (m, o) = from.get_transform_coeffs(&to_system, is_x)?; + + let name = field.name(); + let expr = col(&name) * lit(m) + lit(o); + current_df = current_df.with_column(&name, expr)?; + } + } + } + + Ok(Self { + df: current_df, + schema: self.schema.clone(), + coordinate_system: Some(to_system), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub fn transform_orientation( + &self, + mode: OrientationMode, + ) -> TrackingDataContainerResult { + let current = self.orientation.as_ref().ok_or_else(|| { + crate::error::TrackingDataContainerErrorKind::InvalidParameters( + "No orientation".to_string(), + ) + })?; + if current == &mode { + return Ok(self.clone()); + } + + let p_col_name = self + .schema + .fields + .iter() + .find(|f| { + matches!( + f, + DataField::Frame { + attribute: crate::schema::FrameAttribute::Period + } + ) + }) + .map(|f| f.name()) + .ok_or_else(|| { + crate::error::TrackingDataContainerErrorKind::SchemaError( + "Missing period column".to_string(), + ) + })?; + + let poss_team_col_name = self + .schema + .fields + .iter() + .find(|f| { + matches!( + f, + DataField::Frame { + attribute: crate::schema::FrameAttribute::BallPossTeamId + } + ) + }) + .map(|f| f.name()); + + let mut current_df = self.df.clone(); + + for field in self.schema.fields.iter() { + if let DataField::TrackedEntity { attribute, .. } = field + && matches!( + attribute, + crate::schema::TrackedEntityAttribute::X + | crate::schema::TrackedEntityAttribute::Y + ) + { + let is_x = matches!(attribute, crate::schema::TrackedEntityAttribute::X); + let (m, o) = self + .coordinate_system + .as_ref() + .map(|cs| cs.get_flip_coeffs(is_x)) + .unwrap_or((-1.0, 0.0)); + + let name = field.name(); + + let should_flip_current = + self.get_flip_expr(current, &p_col_name, &poss_team_col_name); + let should_flip_target = + self.get_flip_expr(&mode, &p_col_name, &poss_team_col_name); + + let change_needed = (should_flip_current + .clone() + .and(not(should_flip_target.clone()))) + .or(not(should_flip_current).and(should_flip_target)); + + let flipped_expr = col(&name) * lit(m) + lit(o); + let expr = when(change_needed, flipped_expr).otherwise(col(&name))?; + + current_df = current_df.with_column(&name, expr)?; + } + } + + Ok(Self { + df: current_df, + schema: self.schema.clone(), + coordinate_system: self.coordinate_system.clone(), + orientation: Some(mode), + schema_id: self.schema_id.clone(), + }) + } + + fn get_flip_expr( + &self, + mode: &OrientationMode, + p_col: &str, + poss_col: &Option, + ) -> Expr { + match mode { + OrientationMode::HomeAway => col(p_col).eq(lit("second_half")), + OrientationMode::AwayHome => col(p_col).eq(lit("first_half")), + OrientationMode::BallOwningTeam => { + if let Some(c) = poss_col { + col(c).eq(lit("away")) + } else { + lit(false) + } + } + _ => lit(false), + } + } + + pub fn add_column( + &self, + name: &str, + expr: datafusion::prelude::Expr, + ) -> TrackingDataContainerResult { + self.with_expr_column(name, expr) + } + + pub fn add_columns( + &self, + names: Vec, + exprs: Vec, + ) -> TrackingDataContainerResult { + let mut df = self.df.clone(); + let mut new_schema_fields = self.schema.fields.clone(); + + for (name, expr) in names.into_iter().zip(exprs) { + df = df.with_column(&name, expr)?; + let field = DataField::from_name(&name); + let mut found = false; + for existing_field in &mut new_schema_fields { + if existing_field.name() == name { + *existing_field = field.clone(); + found = true; + break; + } + } + if !found { + new_schema_fields.push(field); + } + } + + Ok(Self { + df, + schema: Fields { + fields: new_schema_fields, + }, + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub fn melt(&self) -> TrackingDataContainerResult { + use crate::schema::TrackedEntity; + use crate::schema::attribute::Attribute; + use datafusion::logical_expr::ExprSchemable; + use std::collections::{HashMap, HashSet}; + + let core_names: HashSet<&str> = [ + "match/id", + "competition/id", + "season/id", + "timestamp", + "frame_id", + "period", + "period_id", + "ball_status", + "ball_poss_team_id", + "ball_poss_status", + ] + .iter() + .cloned() + .collect(); + + let mut core_cols = Vec::new(); + for f in &self.schema.fields { + let name = f.name(); + if core_names.contains(name.as_str()) { + core_cols.push(name); + } + } + + let mut entity_groups: HashMap> = HashMap::new(); + let mut all_attributes: HashSet = HashSet::new(); + let mut attr_datatypes: HashMap = HashMap::new(); + + let df_schema = self.df.schema(); + + for f in &self.schema.fields { + if let DataField::TrackedEntity { entity, attribute } = f { + let attr_name = attribute.name().to_string(); + if attr_name == "player/id" || attr_name == "team/id" || attr_name == "entity_key" { + continue; + } + all_attributes.insert(attr_name.clone()); + entity_groups + .entry(entity.clone()) + .or_default() + .insert(attr_name.clone(), f.name()); + + if !attr_datatypes.contains_key(&attr_name) + && let Ok(arrow_field) = df_schema.field_with_unqualified_name(&f.name()) + { + let mut dt = arrow_field.data_type().clone(); + if attr_name == "x" || attr_name == "y" || attr_name == "z" { + dt = arrow_schema::DataType::Float32; + } + attr_datatypes.insert(attr_name, dt); + } + } + } + + let mut all_attributes_vec: Vec = all_attributes.into_iter().collect(); + all_attributes_vec.sort(); + + let mut melted_dfs = Vec::new(); + + for (entity, attrs) in entity_groups { + let mut exprs = Vec::new(); + for c in &core_cols { + exprs.push(col(c)); + } + + let etype = match &entity { + TrackedEntity::Ball => "ball", + TrackedEntity::Player { .. } => "player", + TrackedEntity::Referee { .. } => "referee", + TrackedEntity::Team { .. } => "team", + }; + + let ekey = match &entity { + TrackedEntity::Ball => "ball".to_string(), + TrackedEntity::Player { team_id, player_id } => { + format!("{}|{}", player_id, team_id) + } + TrackedEntity::Referee { referee_id } => format!("referee:{}", referee_id), + TrackedEntity::Team { team_id } => format!("team:{}", team_id), + }; + + let parts: Vec<&str> = ekey.split('|').collect(); + let p_id = parts[0].to_string(); + let t_id = if parts.len() > 1 { + parts[1].to_string() + } else { + "none".to_string() + }; + + exprs.push(lit(p_id).alias("player/id")); + exprs.push( + lit(if t_id == "none" { + datafusion::scalar::ScalarValue::Utf8(None) + } else { + datafusion::scalar::ScalarValue::Utf8(Some(t_id)) + }) + .alias("team/id"), + ); + exprs.push(lit(ekey).alias("entity_key")); + exprs.push(lit(etype).alias("entity_type")); + + let mut filter_exprs = Vec::new(); + + for attr in &all_attributes_vec { + if let Some(col_name) = attrs.get(attr) { + let mut e = col(col_name); + if attr == "x" || attr == "y" || attr == "z" { + e = e.cast_to(&arrow_schema::DataType::Float32, df_schema)?; + } + exprs.push(e.alias(attr)); + filter_exprs.push(col(col_name).is_not_null()); + } else { + let dt = attr_datatypes + .get(attr) + .cloned() + .unwrap_or(arrow_schema::DataType::Float32); + exprs.push( + datafusion::logical_expr::Expr::Cast(datafusion::logical_expr::Cast { + expr: Box::new(lit(datafusion::scalar::ScalarValue::Null)), + data_type: dt, + }) + .alias(attr), + ); + } + } + + let filter_expr = filter_exprs.into_iter().reduce(|a, b| a.or(b)); + let mut entity_df = self.df.clone(); + if let Some(f) = filter_expr { + entity_df = entity_df.filter(f)?; + } + entity_df = entity_df.select(exprs)?; + melted_dfs.push(entity_df); + } + + if melted_dfs.is_empty() { + return Ok(self.df.clone().limit(0, Some(0))?); + } + + let mut final_df = melted_dfs[0].clone(); + for df in melted_dfs.into_iter().skip(1) { + final_df = final_df.union(df)?; + } + + Ok(final_df) + } + + pub fn add_array_column( + &self, + _name: &str, + _array: arrow_array::ArrayRef, + ) -> TrackingDataContainerResult { + Err(datafusion::error::DataFusionError::NotImplemented("Adding raw arrays to a lazy container is not yet supported. Use the eager container for direct array additions.".to_string()).into()) + } + + pub fn add_column_udf( + &self, + _name: &str, + _func: std::sync::Arc, + _on: Vec, + ) -> TrackingDataContainerResult { + Err(datafusion::error::DataFusionError::NotImplemented( + "Lazy Python UDFs not yet supported".to_string(), + ) + .into()) + } + + pub fn with_expr_column( + &self, + name: &str, + expr: datafusion::prelude::Expr, + ) -> TrackingDataContainerResult { + let df = self.df.clone().with_column(name, expr)?; + let mut new_schema_fields = self.schema.fields.clone(); + + // Update schema + let field = DataField::from_name(name); + let mut found = false; + for existing_field in &mut new_schema_fields { + if existing_field.name() == name { + *existing_field = field.clone(); + found = true; + break; + } + } + if !found { + new_schema_fields.push(field); + } + + Ok(Self { + df, + schema: Fields { + fields: new_schema_fields, + }, + coordinate_system: self.coordinate_system.clone(), + orientation: self.orientation, + schema_id: self.schema_id.clone(), + }) + } + + pub async fn collect(self) -> TrackingDataContainerResult { + let batches = self.df.collect().await?; + if batches.is_empty() { + let mut container = TrackingDataContainer::new( + arrow_array::StructArray::new_empty_fields(0, None), + self.schema, + ); + if let Some(cs) = self.coordinate_system { + container = container.with_coordinate_system(cs); + } + if let Some(om) = self.orientation { + container = container.with_orientation(om); + } + if let Some(id) = self.schema_id { + container = container.with_schema_id(id); + } + return Ok(container); + } + + let batch = if batches.len() == 1 { + batches[0].clone() + } else { + let schema = batches[0].schema(); + arrow_select::concat::concat_batches(&schema, &batches)? + }; + + let mut container = TrackingDataContainer::new(batch.into(), self.schema); + if let Some(cs) = self.coordinate_system { + container = container.with_coordinate_system(cs); + } + if let Some(om) = self.orientation { + container = container.with_orientation(om); + } + if let Some(id) = self.schema_id { + container = container.with_schema_id(id); + } + + Ok(container) + } +} diff --git a/crates/tdc-core/src/datacontainer/mod.rs b/crates/tdc-core/src/datacontainer/mod.rs new file mode 100644 index 0000000..5cb809f --- /dev/null +++ b/crates/tdc-core/src/datacontainer/mod.rs @@ -0,0 +1,7 @@ +pub mod builder; +pub mod core; +pub mod lazy; + +pub use builder::TrackingDataBuilder; +pub use core::TrackingDataContainer; +pub use lazy::LazyTrackingDataContainer; diff --git a/crates/tdc-core/src/error.rs b/crates/tdc-core/src/error.rs new file mode 100644 index 0000000..eb9fcfb --- /dev/null +++ b/crates/tdc-core/src/error.rs @@ -0,0 +1,147 @@ +use std::{error::Error, fmt::Display}; + +/// The kind of a tracking data container error. +#[derive(Debug)] +pub enum TrackingDataContainerErrorKind { + InvalidParameters(String), + SchemaError(String), + SchemaWarning(String), + ArrowError(arrow::error::ArrowError), + DataFusionError(datafusion::error::DataFusionError), + ParquetError(parquet::errors::ParquetError), + IOError(std::io::Error), + Internal(String), +} + +impl Display for TrackingDataContainerErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrackingDataContainerErrorKind::InvalidParameters(err) => { + write!(f, "invalid parameters: {err}") + } + TrackingDataContainerErrorKind::SchemaError(err) => { + write!(f, "schema error: {err}") + } + TrackingDataContainerErrorKind::SchemaWarning(warn) => { + write!(f, "schema warning: {warn}") + } + TrackingDataContainerErrorKind::ArrowError(err) => { + write!(f, "arrow error: {err}") + } + TrackingDataContainerErrorKind::DataFusionError(err) => { + write!(f, "datafusion error: {err}") + } + TrackingDataContainerErrorKind::ParquetError(err) => { + write!(f, "parquet error: {err}") + } + TrackingDataContainerErrorKind::IOError(err) => { + write!(f, "io error: {err}") + } + TrackingDataContainerErrorKind::Internal(err) => { + write!(f, "internal error: {err}") + } + } + } +} + +/// A `tracking data container` error. +/// +/// Contains a kind and a context. Use the `Display` trait to format the +/// error message with its context. +#[derive(Debug)] +pub struct TrackingDataContainerError { + pub kind: TrackingDataContainerErrorKind, + pub context: Vec, +} + +pub trait ErrorContext { + fn with_context(self, ctx_fn: F) -> Self + where + F: FnOnce() -> S; +} + +impl TrackingDataContainerError { + pub fn new(kind: TrackingDataContainerErrorKind) -> Self { + Self { + kind, + context: vec![], + } + } +} + +impl Display for TrackingDataContainerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{kind}", kind = self.kind)?; + if !self.context.is_empty() { + writeln!(f, "\nContext:")?; + + self.context + .iter() + .enumerate() + .try_for_each(|(idx, ctx_value)| writeln!(f, " {idx}: {ctx_value}"))?; + } + Ok(()) + } +} + +impl Error for TrackingDataContainerError {} + +impl ErrorContext for TrackingDataContainerError { + fn with_context(mut self, ctx_fn: F) -> Self + where + F: FnOnce() -> S, + { + self.context.push(ctx_fn().to_string()); + self + } +} + +impl From for TrackingDataContainerError { + fn from(kind: TrackingDataContainerErrorKind) -> Self { + TrackingDataContainerError::new(kind) + } +} + +impl From for TrackingDataContainerError { + fn from(err: arrow::error::ArrowError) -> Self { + TrackingDataContainerErrorKind::ArrowError(err).into() + } +} + +impl From for TrackingDataContainerError { + fn from(err: datafusion::error::DataFusionError) -> Self { + TrackingDataContainerErrorKind::DataFusionError(err).into() + } +} + +impl From for TrackingDataContainerError { + fn from(err: parquet::errors::ParquetError) -> Self { + TrackingDataContainerErrorKind::ParquetError(err).into() + } +} + +impl From for TrackingDataContainerError { + fn from(err: std::io::Error) -> Self { + TrackingDataContainerErrorKind::IOError(err).into() + } +} + +impl From for TrackingDataContainerError { + fn from(err: String) -> Self { + TrackingDataContainerErrorKind::Internal(err).into() + } +} + +pub type TrackingDataContainerResult = Result; + +impl ErrorContext for TrackingDataContainerResult { + fn with_context(self, ctx_fn: F) -> Self + where + F: FnOnce() -> S, + { + match self { + Ok(_) => self, + Err(e) => Err(e.with_context(ctx_fn)), + } + } +} diff --git a/crates/tdc-core/src/io.rs b/crates/tdc-core/src/io.rs new file mode 100644 index 0000000..096a7f4 --- /dev/null +++ b/crates/tdc-core/src/io.rs @@ -0,0 +1,104 @@ +use crate::datacontainer::TrackingDataContainer; +use crate::error::TrackingDataContainerResult; +use crate::schema::{Fields, SchemaId}; +use arrow_array::RecordBatch; +use parquet::arrow::ArrowWriter; +use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder; +use parquet::file::properties::WriterProperties; +use std::fs::File; + +impl TrackingDataContainer { + /// Saves the container to a Parquet file. + pub fn save_parquet(&self, path: &str) -> TrackingDataContainerResult<()> { + let file = File::create(path)?; + let batch = RecordBatch::from(self.data.clone()); + + let mut key_value_metadata = Vec::new(); + + if let Some(cs) = &self.coordinate_system { + let cs_json = serde_json::to_string(cs).map_err(|e| e.to_string())?; + key_value_metadata.push(parquet::format::KeyValue::new( + "tdc:coordinate_system".to_string(), + Some(cs_json), + )); + } + + if let Some(om) = &self.orientation { + let om_json = serde_json::to_string(om).map_err(|e| e.to_string())?; + key_value_metadata.push(parquet::format::KeyValue::new( + "tdc:orientation_mode".to_string(), + Some(om_json), + )); + } + + if let Some(id) = &self.schema_id { + key_value_metadata.push(parquet::format::KeyValue::new( + "tdc:schema".to_string(), + Some(id.to_string()), + )); + } + + let props_builder = + WriterProperties::builder().set_key_value_metadata(Some(key_value_metadata)); + + let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props_builder.build()))?; + writer.write(&batch)?; + writer.close()?; + + Ok(()) + } + + /// Loads a container from a Parquet file. + pub fn load_parquet(path: &str) -> TrackingDataContainerResult { + let file = File::open(path)?; + let builder = ParquetRecordBatchReaderBuilder::try_new(file)?; + + let mut coordinate_system = None; + let mut orientation = None; + let mut schema_id = None; + + if let Some(metadata) = builder.metadata().file_metadata().key_value_metadata() { + for kv in metadata { + if kv.key == "tdc:coordinate_system" { + if let Some(val) = &kv.value { + coordinate_system = + Some(serde_json::from_str(val).map_err(|e| e.to_string())?); + } + } else if kv.key == "tdc:orientation_mode" + && let Some(val) = &kv.value + { + orientation = Some(serde_json::from_str(val).map_err(|e| e.to_string())?); + } else if kv.key == "tdc:schema" + && let Some(val) = &kv.value + { + schema_id = SchemaId::parse(val); + } + } + } + + let mut reader = builder.build()?; + let batch_result = reader.next(); + let batch = match batch_result { + Some(Ok(b)) => b, + Some(Err(e)) => return Err(e.into()), + None => return Err("Empty parquet file".to_string().into()), + }; + + let schema_fields: Vec<_> = batch + .schema() + .fields() + .iter() + .map(|f| crate::schema::DataField::from_name(f.name())) + .collect(); + let schema = Fields { + fields: schema_fields, + }; + + let mut container = Self::new(batch.into(), schema); + container.coordinate_system = coordinate_system; + container.orientation = orientation; + container.schema_id = schema_id; + + Ok(container) + } +} diff --git a/crates/tdc-core/src/lib.rs b/crates/tdc-core/src/lib.rs new file mode 100644 index 0000000..012aa60 --- /dev/null +++ b/crates/tdc-core/src/lib.rs @@ -0,0 +1,14 @@ +pub mod config; +pub mod datacontainer; +pub mod error; +pub mod io; +pub mod schema; +pub mod selector; + +pub use datacontainer::{LazyTrackingDataContainer, TrackingDataBuilder, TrackingDataContainer}; +pub use schema::{ + Attribute, CoordinateSystem, DataField, Direction, Fields, FrameAttribute, LengthUnit, + OrientationMode, Origin, Period, SchemaDefinition, SchemaId, TrackedEntity, + TrackedEntityAttribute, default_schema_id, get_schema, list_schemas, +}; +pub use selector::Select; diff --git a/crates/tdc-core/src/schema/attribute.rs b/crates/tdc-core/src/schema/attribute.rs new file mode 100644 index 0000000..8317a54 --- /dev/null +++ b/crates/tdc-core/src/schema/attribute.rs @@ -0,0 +1,186 @@ +//! Definitions for frame and entity attributes. +//! +//! This module provides traits and enums for representing various data points captured +//! in sports tracking, such as spatial coordinates, velocity, and match metadata. + +use arrow_schema::{DataType, TimeUnit}; +use std::sync::Arc; +use strum_macros::{AsRefStr, Display, EnumString}; + +/// Common trait for all schema attributes. +pub trait Attribute { + /// Returns the standardized string representation of the attribute. + fn name(&self) -> &str; + + /// Returns the default Apache Arrow data type used to represent this attribute in memory. + fn default_arrow_type(&self) -> DataType; + + /// Returns true if this attribute typically has low cardinality (few unique values). + fn is_low_cardinality(&self) -> bool; +} + +/// Attributes specifically associated with a tracked entity (Player, Ball, or Referee). +/// +/// These attributes represent spatial and physical data points captured for an entity +/// during a match. +#[derive(Clone, Debug, PartialEq, Eq, Hash, EnumString, AsRefStr, Display)] +#[strum(serialize_all = "snake_case")] +pub enum TrackedEntityAttribute { + /// Unique identifier for the entity (e.g., person ID or ball ID). + Id, + /// X-coordinate on the pitch. + X, + /// Y-coordinate on the pitch. + Y, + /// Z-coordinate (altitude/height) on the pitch. + Z, + /// Distance traveled since the last capture point. + Dist, + /// Instantaneous velocity or speed. + Vel, + /// Instantaneous acceleration. + Acc, + /// Latitude (GPS). + Lat, + /// Longitude (GPS). + Long, + /// Whether the entity was visible to the tracking system at this frame. + IsVisible, + /// Cumulative minutes played by the entity. + MinutesPlayed, + + // Landmark / Skeletal attributes + /// Unique identifier for a landmark. + LandmarkIndex(String), + /// Relative x coordinate of landmark. + LandmarkX(String), + /// Relative y coordinate of landmark. + LandmarkY(String), + /// Relative z coordinate of landmark. + LandmarkZ(String), + /// List of children indexes associated with keypoint. + LandmarkChildren(String), + /// If landmark is detected (true) or inferred (false). + LandmarkIsVisible(String), + + // Team specific + /// Official name of the team or entity. + Name, + /// Jersey color representation (e.g., hex code or name). + JerseyColour, + /// Tactical formation (e.g., "4-3-3"). + Formation, + + /// Catch-all for custom attributes not defined in the core schema (e.g., "heart_rate"). + Other(String), +} + +impl Attribute for TrackedEntityAttribute { + fn name(&self) -> &str { + match self { + Self::Other(s) => s, + Self::LandmarkIndex(_) => "index", + Self::LandmarkX(_) => "x", + Self::LandmarkY(_) => "y", + Self::LandmarkZ(_) => "z", + Self::LandmarkChildren(_) => "children", + Self::LandmarkIsVisible(_) => "is_visible", + _ => self.as_ref(), + } + } + + /// Returns the default Arrow column type for this attribute. + fn default_arrow_type(&self) -> DataType { + use TrackedEntityAttribute::*; + match self { + Id | Other(_) | Name | JerseyColour | Formation | LandmarkIndex(_) => DataType::Utf8, + IsVisible | LandmarkIsVisible(_) => DataType::Boolean, + X | Y | Z | Dist | Vel | Acc | Lat | Long | MinutesPlayed | LandmarkX(_) + | LandmarkY(_) | LandmarkZ(_) => DataType::Float32, + LandmarkChildren(_) => DataType::List(Arc::new(arrow_schema::Field::new( + "item", + DataType::Int32, + true, + ))), + } + } + + fn is_low_cardinality(&self) -> bool { + use TrackedEntityAttribute::*; + matches!(self, Formation | JerseyColour | LandmarkIndex(_)) + } +} + +// ----------------------------------------------------------------------------- +// FrameAttribute +// ----------------------------------------------------------------------------- + +/// Attributes associated with a global frame or the match state. +/// +/// These attributes provide context for the entire set of positions captured +/// at a single point in time. +#[derive(Clone, Debug, PartialEq, Eq, Hash, EnumString, AsRefStr, Display)] +#[strum(serialize_all = "snake_case")] +pub enum FrameAttribute { + /// Unique sequence number for the frame. + FrameId, + /// Match period (e.g., 1 for first half, 2 for second half). + Period, + /// Wall-clock timestamp of the capture. + Timestamp, + + /// Unique identifier for the match. + /// + /// "match" is a reserved keyword in Rust, so MatchId is used as an alternative. + /// The strum attribute ensures it parses from the string "match" correctly. + #[strum(serialize = "match")] + MatchId, + + /// Global ball status (e.g., "alive" or "dead"). + BallStatus, + /// Identifier of the team currently in possession of the ball. + BallPossTeamId, + /// Status of ball possession (e.g., "home", "away", "none"). + BallPossStatus, + + /// Name of the data vendor's event stream. + VendorEventName, + /// Name of the data vendor's tracking system. + VendorTrackingName, + + /// Catch-all for custom frame-level metadata. + Other(String), +} + +impl Attribute for FrameAttribute { + fn name(&self) -> &str { + match self { + Self::Other(s) => s, + _ => self.as_ref(), + } + } + + /// Returns the default Arrow column type for this attribute. + fn default_arrow_type(&self) -> DataType { + use FrameAttribute::*; + match self { + FrameId => DataType::UInt64, + Period => DataType::Utf8, + Timestamp => DataType::Timestamp(TimeUnit::Microsecond, None), + BallStatus => DataType::Boolean, + MatchId | BallPossTeamId | BallPossStatus | VendorEventName | VendorTrackingName => { + DataType::Utf8 + } + Other(name) if name == "x" || name == "y" || name == "z" => DataType::Float32, + Other(_) => DataType::Utf8, + } + } + + fn is_low_cardinality(&self) -> bool { + use FrameAttribute::*; + matches!( + self, + Period | BallStatus | BallPossStatus | VendorEventName | VendorTrackingName + ) + } +} diff --git a/crates/tdc-core/src/schema/coordinate_system.rs b/crates/tdc-core/src/schema/coordinate_system.rs new file mode 100644 index 0000000..f21fbe1 --- /dev/null +++ b/crates/tdc-core/src/schema/coordinate_system.rs @@ -0,0 +1,216 @@ +//! Models for spatial metadata and coordinate system definitions. +//! +//! This module provides the tools to describe how coordinates are mapped to the +//! physical world, enabling transformations between different providers and standards. + +use crate::error::{TrackingDataContainerErrorKind, TrackingDataContainerResult}; +use serde::{Deserialize, Serialize}; +use strum_macros::{AsRefStr, Display, EnumString}; + +/// Defines a coordinate system for tracking data. +/// +/// A coordinate system combines spatial bounds (ranges), an origin point, axis orientations, +/// and measurement units to provide a complete context for entity positions. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CoordinateSystem { + /// The valid range of X coordinates (e.g., Some((-52.5, 52.5))). + pub x_range: Option<(f64, f64)>, + /// The valid range of Y coordinates (e.g., Some((-34.0, 34.0))). + pub y_range: Option<(f64, f64)>, + /// The location of the (0,0) point. + pub origin: Origin, + /// The direction in which X values increase. + pub x_direction: Direction, + /// The direction in which Y values increase. + pub y_direction: Direction, + /// The physical unit of length for the coordinates. + pub length_unit: Option, +} + +/// The location of the origin (0,0) in a coordinate system. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr, Display, +)] +#[strum(serialize_all = "snake_case")] +pub enum Origin { + /// The center of the pitch/court. + Center, + /// The top-left corner of the pitch (often used in computer vision). + TopLeft, + /// The bottom-left corner of the pitch. + BottomLeft, +} + +/// Possible axis directions. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr, Display, +)] +#[strum(serialize_all = "snake_case")] +pub enum Direction { + /// Values increase towards the right. + Right, + /// Values increase towards the left. + Left, + /// Values increase towards the top. + Up, + /// Values increase towards the bottom. + Down, +} + +/// Standardized units of length. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr, Display, +)] +#[strum(serialize_all = "snake_case")] +pub enum LengthUnit { + /// Standard metric meter. + Meters, + /// Centimeter. + Centimeters, + /// Millimeter. + Millimeters, + /// Yard (approx 0.9144 meters). + Yards, + /// Inch. + Inches, + /// Foot. + Feet, + /// Normalized unit where coordinates represent a percentage of pitch dimensions. + Percent, +} + +/// Logic for normalizing playing direction across periods or possession. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr, Display, +)] +#[strum(serialize_all = "snake_case")] +pub enum OrientationMode { + /// Coordinates are flipped so the team in possession always attacks from left to right. + BallOwningTeam, + /// The home team attacks left-to-right in the first half and right-to-left in the second. + HomeAway, + /// The away team attacks left-to-right in the first half and right-to-left in the second. + AwayHome, + /// The home team always attacks from left to right, regardless of period. + StaticHomeAway, + /// The away team always attacks from left to right, regardless of period. + StaticAwayHome, +} + +impl CoordinateSystem { + /// Creates a new coordinate system definition. + pub fn new( + x_range: Option<(f64, f64)>, + y_range: Option<(f64, f64)>, + origin: Origin, + x_direction: Direction, + y_direction: Direction, + length_unit: Option, + ) -> Self { + Self { + x_range, + y_range, + origin, + x_direction, + y_direction, + length_unit, + } + } +} + +impl CoordinateSystem { + pub fn get_transform_coeffs( + &self, + to: &CoordinateSystem, + is_x: bool, + ) -> TrackingDataContainerResult<(f64, f64)> { + let (f_min, f_max) = if is_x { self.x_range } else { self.y_range }.ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters( + if is_x { + "Source X range undefined" + } else { + "Source Y range undefined" + } + .to_string(), + ) + })?; + let (t_min, t_max) = if is_x { to.x_range } else { to.y_range }.ok_or_else(|| { + TrackingDataContainerErrorKind::InvalidParameters( + if is_x { + "Target X range undefined" + } else { + "Target Y range undefined" + } + .to_string(), + ) + })?; + + let f_scale = f_max - f_min; + let t_scale = t_max - t_min; + + let f_unit = self.length_unit.unwrap_or(LengthUnit::Meters).multiplier(); + let t_unit = to.length_unit.unwrap_or(LengthUnit::Meters).multiplier(); + + let f_sign = if is_x { + if self.x_direction == Direction::Right { + 1.0 + } else { + -1.0 + } + } else if self.y_direction == Direction::Up { + 1.0 + } else { + -1.0 + }; + let t_sign = if is_x { + if to.x_direction == Direction::Right { + 1.0 + } else { + -1.0 + } + } else if to.y_direction == Direction::Up { + 1.0 + } else { + -1.0 + }; + + // Point mapping: physical center maps to physical center. + let f_center_coord = if self.origin == Origin::Center { + 0.0 + } else { + (f_min + f_max) / 2.0 + }; + let t_center_coord = if to.origin == Origin::Center { + 0.0 + } else { + (t_min + t_max) / 2.0 + }; + + let mul = (t_scale / f_scale) * (f_unit / t_unit) * f_sign * t_sign; + let off = t_center_coord - f_center_coord * mul; + + Ok((mul, off)) + } + + /// Returns the multiplier and offset to flip a coordinate across the center of this system. + pub fn get_flip_coeffs(&self, is_x: bool) -> (f64, f64) { + let (min, max) = if is_x { self.x_range } else { self.y_range }.unwrap_or((-50.0, 50.0)); + let center = (min + max) / 2.0; + // f(v) = center - (v - center) = 2*center - v + (-1.0, 2.0 * center) + } +} + +impl LengthUnit { + pub fn multiplier(&self) -> f64 { + match self { + LengthUnit::Meters => 1.0, + LengthUnit::Centimeters => 0.01, + LengthUnit::Millimeters => 0.001, + LengthUnit::Yards => 0.9144, + LengthUnit::Feet => 0.3048, + LengthUnit::Inches => 0.0254, + LengthUnit::Percent => 1.0, + } + } +} diff --git a/crates/tdc-core/src/schema/definition.rs b/crates/tdc-core/src/schema/definition.rs new file mode 100644 index 0000000..af8be33 --- /dev/null +++ b/crates/tdc-core/src/schema/definition.rs @@ -0,0 +1,213 @@ +//! Schema identification and definition types. +//! +//! This module provides the core types for multi-schema support, allowing +//! TDC to validate data against different schema definitions (e.g., 2D tracking, +//! skeletal tracking, GPS data) for different sports and versions. + +use super::{Attribute, FrameAttribute, TrackedEntityAttribute}; +use crate::error::{ + TrackingDataContainerError, TrackingDataContainerErrorKind, TrackingDataContainerResult, +}; +use std::fmt; + +/// Identifies a specific schema by sport, data kind, and version. +/// +/// # Examples +/// +/// - CDF tracking: `SchemaId::new("cdf", "tracking", "0.2.3")` +/// - CDF skeletal: `SchemaId::new("cdf", "skeletal", "0.2.3")` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SchemaId { + /// The sport or standard this schema applies to (e.g., "cdf", "football"). + pub sport: String, + /// The kind of data (e.g., "tracking", "skeletal"). + pub data_kind: String, + /// The version string (e.g., "0.2.2"). + pub version: String, +} + +impl SchemaId { + /// Creates a new schema identifier. + pub fn new( + sport: impl Into, + data_kind: impl Into, + version: impl Into, + ) -> Self { + Self { + sport: sport.into(), + data_kind: data_kind.into(), + version: version.into(), + } + } + + /// Parses a schema ID from a slash-separated string like "football/tracking_2d/0.2.2". + pub fn parse(s: &str) -> Option { + let parts: Vec<&str> = s.split('/').collect(); + if parts.len() == 3 { + Some(Self::new(parts[0], parts[1], parts[2])) + } else { + None + } + } +} + +impl fmt::Display for SchemaId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}/{}", self.sport, self.data_kind, self.version) + } +} + +/// Defines what entities a schema requires. +#[derive(Clone)] +pub struct EntityRequirement { + /// The validation function that checks if this requirement is met. + pub check: fn(&super::Fields) -> bool, + /// The error message when this requirement is not met. + pub error_field: String, +} + +impl fmt::Debug for EntityRequirement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EntityRequirement") + .field("error_field", &self.error_field) + .finish() + } +} + +/// Defines allowed values for a specific column. +#[derive(Clone)] +pub struct ValueRequirement { + /// The column name to check. + pub column: String, + /// The list of allowed values. + pub allowed_values: Vec, +} + +impl fmt::Debug for ValueRequirement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ValueRequirement") + .field("column", &self.column) + .field("allowed_values", &self.allowed_values) + .finish() + } +} + +/// A schema definition containing validation rules for a specific data format. +/// +/// Each schema defines which frame attributes, ball attributes, and entity structures +/// are required for valid data. +#[derive(Clone)] +pub struct SchemaDefinition { + /// The unique schema identifier. + pub id: SchemaId, + /// Human-readable description of the schema. + pub description: String, + /// Required frame-level attributes (e.g., FrameId, Timestamp). + pub required_frame_attributes: Vec, + /// Required ball attributes (empty if no ball in this schema). + pub required_ball_attributes: Vec, + /// Structural entity requirements (e.g., "at least one team with players"). + pub entity_requirements: Vec, + /// Value constraints for specific columns. + pub value_requirements: Vec, + /// Core column names for this schema (used in pivot/melt operations). + pub core_columns: Vec, +} + +impl SchemaDefinition { + /// Validates a `Fields` collection against this schema's requirements. + /// + /// Returns `Ok(())` if all required fields are present, or an error + /// describing which fields are missing. + pub fn validate(&self, fields: &super::Fields) -> TrackingDataContainerResult<()> { + let mut missing = Vec::new(); + + // 1. Check required frame attributes + for attr in &self.required_frame_attributes { + if !fields.has_frame_attr(attr.clone()) { + let name = match attr { + FrameAttribute::MatchId => "match/id".to_string(), + _ => attr.name().to_string(), + }; + missing.push(name); + } + } + + // 2. Check required ball attributes + for attr in &self.required_ball_attributes { + if !fields.has_ball_attr(attr.clone()) { + missing.push(format!("ball/{}", attr.name())); + } + } + + // 3. Check entity requirements + for req in &self.entity_requirements { + if !(req.check)(fields) { + missing.push(req.error_field.clone()); + } + } + + if !missing.is_empty() { + return Err(TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaError(format!( + "Schema '{}' validation failed. Missing required fields: {}", + self.id, + missing.join(", ") + )), + )); + } + + // 4. Check for unsupported fields (warning) + fields.check_unsupported_fields() + } +} + +impl fmt::Debug for SchemaDefinition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SchemaDefinition") + .field("id", &self.id) + .field("description", &self.description) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_schema_id_new() { + let id = SchemaId::new("football", "tracking_2d", "0.2.2"); + assert_eq!(id.sport, "football"); + assert_eq!(id.data_kind, "tracking_2d"); + assert_eq!(id.version, "0.2.2"); + } + + #[test] + fn test_schema_id_display() { + let id = SchemaId::new("football", "tracking_2d", "0.2.2"); + assert_eq!(id.to_string(), "football/tracking_2d/0.2.2"); + } + + #[test] + fn test_schema_id_parse() { + let id = SchemaId::parse("football/tracking_2d/0.2.2").unwrap(); + assert_eq!(id, SchemaId::new("football", "tracking_2d", "0.2.2")); + } + + #[test] + fn test_schema_id_parse_invalid() { + assert!(SchemaId::parse("football/tracking_2d").is_none()); + assert!(SchemaId::parse("football").is_none()); + assert!(SchemaId::parse("").is_none()); + } + + #[test] + fn test_schema_id_equality() { + let a = SchemaId::new("football", "tracking_2d", "0.2.2"); + let b = SchemaId::new("football", "tracking_2d", "0.2.2"); + let c = SchemaId::new("football", "skeletal", "0.1.0"); + assert_eq!(a, b); + assert_ne!(a, c); + } +} diff --git a/crates/tdc-core/src/schema/entity.rs b/crates/tdc-core/src/schema/entity.rs new file mode 100644 index 0000000..cf50b34 --- /dev/null +++ b/crates/tdc-core/src/schema/entity.rs @@ -0,0 +1,46 @@ +//! Definitions for tracked entities. + +/// Represents an entity on the pitch/court. +/// +/// Tracked entities are the primary subjects of the tracking data, providing the context +/// for spatial coordinates and other time-series attributes. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TrackedEntity { + /// The match ball. + Ball, + /// A player belonging to a specific team. + Player { + /// The unique identifier for the team. + team_id: String, + /// The unique identifier for the player. + player_id: String, + }, + /// A match official. + Referee { + /// The unique identifier for the referee. + referee_id: String, + }, + /// Represents team-level data (e.g., formation or possession) not tied to a specific player. + Team { + /// The unique identifier for the team. + team_id: String, + }, +} + +impl TrackedEntity { + pub fn is_ball(&self) -> bool { + matches!(self, TrackedEntity::Ball) + } + + pub fn is_player(&self) -> bool { + matches!(self, TrackedEntity::Player { .. }) + } + + pub fn is_team(&self) -> bool { + matches!(self, TrackedEntity::Team { .. }) + } + + pub fn is_referee(&self) -> bool { + matches!(self, TrackedEntity::Referee { .. }) + } +} diff --git a/crates/tdc-core/src/schema/field.rs b/crates/tdc-core/src/schema/field.rs new file mode 100644 index 0000000..ab89095 --- /dev/null +++ b/crates/tdc-core/src/schema/field.rs @@ -0,0 +1,204 @@ +//! Logic for identifying and parsing individual data columns. + +use super::{Attribute, FrameAttribute, TrackedEntity, TrackedEntityAttribute}; +use arrow_schema::DataType; +use std::fmt; +use std::str::FromStr; + +/// Standard core columns that identify the dataset, match, or frame context. +pub const CORE_COLUMNS: &[&str] = &[ + "match/id", + "competition/id", + "season/id", + "timestamp", + "frame_id", + "period", + "period_id", + "player/id", + "team/id", + "entity_key", + "ball_status", + "ball_poss_team_id", + "ball_poss_status", +]; + +/// Represents a single column (field) in a tracking dataset. +/// +/// A `DataField` maps an Arrow column name to its domain-specific meaning, +/// such as a player's X-coordinate or a match period. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum DataField { + /// A global attribute for the entire frame. + Frame { + /// The specific frame-level attribute. + attribute: FrameAttribute, + }, + /// An attribute tied to a specific tracked entity. + TrackedEntity { + /// The entity this field belongs to. + entity: TrackedEntity, + /// The specific attribute being tracked. + attribute: TrackedEntityAttribute, + }, +} + +impl DataField { + /// Serializes the DataField back into its standardized hierarchical string representation. + /// + /// # Examples + /// + /// - Player coordinate: `teams/home/players/10/x` + /// - Ball coordinate: `ball/z` + /// - Global metadata: `frame_id` + pub fn name(&self) -> String { + match self { + DataField::Frame { attribute } => match attribute { + FrameAttribute::VendorEventName => "vendor/event/name".to_string(), + FrameAttribute::VendorTrackingName => "vendor/tracking/name".to_string(), + FrameAttribute::MatchId => "match/id".to_string(), + _ => attribute.name().to_string(), + }, + DataField::TrackedEntity { entity, attribute } => { + let attr_name = attribute.name(); + match entity { + TrackedEntity::Ball => format!("ball/{attr_name}"), + TrackedEntity::Player { team_id, player_id } => match attribute { + TrackedEntityAttribute::LandmarkIndex(j) + | TrackedEntityAttribute::LandmarkX(j) + | TrackedEntityAttribute::LandmarkY(j) + | TrackedEntityAttribute::LandmarkZ(j) + | TrackedEntityAttribute::LandmarkChildren(j) + | TrackedEntityAttribute::LandmarkIsVisible(j) => { + format!("teams/{team_id}/players/{player_id}/landmarks/{j}/{attr_name}") + } + _ => format!("teams/{team_id}/players/{player_id}/{attr_name}"), + }, + TrackedEntity::Referee { referee_id } => { + format!("referees/{referee_id}/{attr_name}") + } + TrackedEntity::Team { team_id } => { + format!("teams/{team_id}/{attr_name}") + } + } + } + } + } + + /// Parses a hierarchical column name path into a domain-specific `DataField`. + /// + /// This method implements the inverse of [`DataField::name`]. + pub fn from_name(path: &str) -> Self { + let parts: Vec<&str> = path.split('/').collect(); + + match parts.as_slice() { + ["teams", tid, "players", pid, "landmarks", j, ..] => { + let attr_str = parts[6..].join("/"); + let attribute = match attr_str.as_str() { + "index" => TrackedEntityAttribute::LandmarkIndex(j.to_string()), + "x" => TrackedEntityAttribute::LandmarkX(j.to_string()), + "y" => TrackedEntityAttribute::LandmarkY(j.to_string()), + "z" => TrackedEntityAttribute::LandmarkZ(j.to_string()), + "children" => TrackedEntityAttribute::LandmarkChildren(j.to_string()), + "is_visible" => TrackedEntityAttribute::LandmarkIsVisible(j.to_string()), + _ => Self::parse_attr(&attr_str), + }; + DataField::TrackedEntity { + entity: TrackedEntity::Player { + team_id: tid.to_string(), + player_id: pid.to_string(), + }, + attribute, + } + } + ["teams", tid, "players", pid, ..] => { + let attr_str = parts[4..].join("/"); + DataField::TrackedEntity { + entity: TrackedEntity::Player { + team_id: tid.to_string(), + player_id: pid.to_string(), + }, + attribute: Self::parse_attr(&attr_str), + } + } + ["teams", tid, ..] => { + let attr_str = parts[2..].join("/"); + DataField::TrackedEntity { + entity: TrackedEntity::Team { + team_id: tid.to_string(), + }, + attribute: Self::parse_attr(&attr_str), + } + } + ["ball", ..] => { + let attr_str = parts[1..].join("/"); + DataField::TrackedEntity { + entity: TrackedEntity::Ball, + attribute: Self::parse_attr(&attr_str), + } + } + ["referees", rid, ..] => { + let attr_str = parts[2..].join("/"); + DataField::TrackedEntity { + entity: TrackedEntity::Referee { + referee_id: rid.to_string(), + }, + attribute: Self::parse_attr(&attr_str), + } + } + ["other", pid, ..] => { + let attr_str = parts[2..].join("/"); + DataField::TrackedEntity { + entity: TrackedEntity::Player { + team_id: "none".to_string(), + player_id: pid.to_string(), + }, + attribute: Self::parse_attr(&attr_str), + } + } + ["vendor", "event", "name"] => DataField::Frame { + attribute: FrameAttribute::VendorEventName, + }, + ["vendor", "tracking", "name"] => DataField::Frame { + attribute: FrameAttribute::VendorTrackingName, + }, + ["match", "id"] => DataField::Frame { + attribute: FrameAttribute::MatchId, + }, + [attr] => DataField::Frame { + attribute: FrameAttribute::from_str(attr) + .unwrap_or_else(|_| FrameAttribute::Other(attr.to_string())), + }, + _ => DataField::Frame { + attribute: FrameAttribute::Other(path.to_string()), + }, + } + } + + /// Helper to safely parse attributes with a fallback to `Other`. + fn parse_attr(s: &str) -> TrackedEntityAttribute { + TrackedEntityAttribute::from_str(s) + .unwrap_or_else(|_| TrackedEntityAttribute::Other(String::from(s))) + } + + /// Returns the recommended Apache Arrow data type for this field. + pub fn default_arrow_type(&self) -> DataType { + match self { + DataField::Frame { attribute } => attribute.default_arrow_type(), + DataField::TrackedEntity { attribute, .. } => attribute.default_arrow_type(), + } + } + + /// Returns true if this field typically has low cardinality. + pub fn is_low_cardinality(&self) -> bool { + match self { + DataField::Frame { attribute } => attribute.is_low_cardinality(), + DataField::TrackedEntity { attribute, .. } => attribute.is_low_cardinality(), + } + } +} + +impl fmt::Display for DataField { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name()) + } +} diff --git a/crates/tdc-core/src/schema/fields.rs b/crates/tdc-core/src/schema/fields.rs new file mode 100644 index 0000000..bcd8f18 --- /dev/null +++ b/crates/tdc-core/src/schema/fields.rs @@ -0,0 +1,333 @@ +//! Logic for managing and validating a collection of data fields. + +use super::{DataField, FrameAttribute, TrackedEntity, TrackedEntityAttribute}; +use crate::error::{ + TrackingDataContainerError, TrackingDataContainerErrorKind, TrackingDataContainerResult, +}; + +/// Represents the ordered collection of fields (columns) in a tracking dataset. +#[derive(Clone, Debug, PartialEq)] +pub struct Fields { + /// The underlying list of domain-specific data fields. + pub fields: Vec, +} + +impl Fields { + /// Creates a new `Fields` collection from a list of hierarchical column names. + pub fn new(columns: Vec) -> Self { + let fields = columns.iter().map(|c| DataField::from_name(c)).collect(); + Self { fields } + } + + /// Returns the index of the specified field, if it exists. + pub fn get_index(&self, field: &DataField) -> Option { + self.fields.iter().position(|f| f == field) + } + + /// Returns the indices of the specified fields in the current schema. + pub fn get_column_indices(&self, fields: &[DataField]) -> Vec> { + let field_map: std::collections::HashMap = self + .fields + .iter() + .enumerate() + .map(|(i, f)| (f.name(), i)) + .collect(); + + fields + .iter() + .map(|f| field_map.get(&f.name()).copied()) + .collect() + } + + /// Returns the field at the specified index, if it exists. + pub fn get(&self, index: usize) -> Option<&DataField> { + self.fields.get(index) + } + + /// Returns the number of fields in the collection. + pub fn len(&self) -> usize { + self.fields.len() + } + + /// Returns `true` if the collection contains no fields. + pub fn is_empty(&self) -> bool { + self.fields.is_empty() + } + + /// Returns a list of unique player IDs present in the fields, optionally filtered by team. + pub fn players(&self, team_id: Option<&str>) -> Vec { + let mut players = std::collections::HashSet::new(); + for field in &self.fields { + if let DataField::TrackedEntity { + entity: + TrackedEntity::Player { + team_id: tid, + player_id, + }, + .. + } = field + && team_id.is_none_or(|t| t == tid) + { + players.insert(player_id.clone()); + } + } + let mut result: Vec = players.into_iter().collect(); + result.sort(); + result + } + + /// Returns a list of unique player entities present in the fields, optionally filtered by team. + pub fn player_entities(&self, team_id: Option<&str>) -> Vec { + let mut players = std::collections::HashSet::new(); + for field in &self.fields { + if let DataField::TrackedEntity { + entity: entity @ TrackedEntity::Player { team_id: tid, .. }, + .. + } = field + && team_id.is_none_or(|t| t == tid) + { + players.insert(entity.clone()); + } + } + let mut result: Vec = players.into_iter().collect(); + result.sort_by_key(|e| match e { + TrackedEntity::Player { team_id, player_id } => (team_id.clone(), player_id.clone()), + _ => unreachable!(), + }); + result + } + + /// Returns a list of unique team IDs present in the fields. + pub fn teams(&self) -> Vec { + let mut teams = std::collections::HashSet::new(); + for field in &self.fields { + match field { + DataField::TrackedEntity { + entity: TrackedEntity::Team { team_id }, + .. + } => { + teams.insert(team_id.clone()); + } + DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id, .. }, + .. + } => { + teams.insert(team_id.clone()); + } + _ => {} + } + } + let mut result: Vec = teams.into_iter().collect(); + result.sort(); + result + } + + /// Returns a list of unique team entities present in the fields. + pub fn team_entities(&self) -> Vec { + let mut teams = std::collections::HashSet::new(); + for field in &self.fields { + match field { + DataField::TrackedEntity { + entity: entity @ TrackedEntity::Team { .. }, + .. + } => { + teams.insert(entity.clone()); + } + DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id, .. }, + .. + } => { + teams.insert(TrackedEntity::Team { + team_id: team_id.clone(), + }); + } + _ => {} + } + } + let mut result: Vec = teams.into_iter().collect(); + result.sort_by_key(|e| match e { + TrackedEntity::Team { team_id } => team_id.clone(), + _ => unreachable!(), + }); + result + } + + /// Returns a list of unique referee IDs present in the fields. + pub fn referees(&self) -> Vec { + let mut referees = std::collections::HashSet::new(); + for field in &self.fields { + if let DataField::TrackedEntity { + entity: TrackedEntity::Referee { referee_id }, + .. + } = field + { + referees.insert(referee_id.clone()); + } + } + let mut result: Vec = referees.into_iter().collect(); + result.sort(); + result + } + + /// Returns a list of unique referee entities present in the fields. + pub fn referee_entities(&self) -> Vec { + let mut referees = std::collections::HashSet::new(); + for field in &self.fields { + if let DataField::TrackedEntity { + entity: entity @ TrackedEntity::Referee { .. }, + .. + } = field + { + referees.insert(entity.clone()); + } + } + let mut result: Vec = referees.into_iter().collect(); + result.sort_by_key(|e| match e { + TrackedEntity::Referee { referee_id } => referee_id.clone(), + _ => unreachable!(), + }); + result + } + + /// Validates the fields against the default schema (CDF v0.2.3 tracking). + /// + /// This is a convenience method that delegates to [`validate_with_schema`] using + /// the default schema ID. + /// + /// # Errors + /// + /// Returns a [`TrackingDataContainerError`] of kind `SchemaError` if required fields are missing. + pub fn validate(&self) -> TrackingDataContainerResult<()> { + let default_id = super::registry::default_schema_id(); + self.validate_with_schema(&default_id) + } + + /// Validates the fields against a specific schema definition. + /// + /// Looks up the schema in the global registry and checks all required fields. + /// + /// # Errors + /// + /// Returns a [`TrackingDataContainerError`] of kind `SchemaError` if the schema is unknown + /// or if required fields are missing. + pub fn validate_with_schema( + &self, + schema_id: &super::definition::SchemaId, + ) -> TrackingDataContainerResult<()> { + let schema_def = super::registry::get_schema(schema_id).ok_or_else(|| { + TrackingDataContainerError::new(TrackingDataContainerErrorKind::SchemaError(format!( + "Unknown schema: {}", + schema_id + ))) + })?; + schema_def.validate(self) + } + + /// Checks for unsupported fields (Other variants) and emits a warning. + pub(crate) fn check_unsupported_fields(&self) -> TrackingDataContainerResult<()> { + let mut unsupported = Vec::new(); + for field in &self.fields { + match field { + DataField::Frame { + attribute: FrameAttribute::Other(name), + } => { + unsupported.push(name.clone()); + } + DataField::TrackedEntity { + attribute: TrackedEntityAttribute::Other(_), + .. + } => { + unsupported.push(field.name()); + } + _ => {} + } + } + + if !unsupported.is_empty() { + return Err(TrackingDataContainerError::new( + TrackingDataContainerErrorKind::SchemaWarning(format!( + "Found unsupported fields: {}", + unsupported.join(", ") + )), + )); + } + + Ok(()) + } + + /// Helper to check for a specific global frame attribute. + pub(crate) fn has_frame_attr(&self, attr: FrameAttribute) -> bool { + self.fields + .iter() + .any(|f| matches!(f, DataField::Frame { attribute } if *attribute == attr)) + } + + /// Helper to check for a specific ball attribute. + pub(crate) fn has_ball_attr(&self, attr: TrackedEntityAttribute) -> bool { + self.fields.iter().any(|f| matches!(f, DataField::TrackedEntity { entity: TrackedEntity::Ball, attribute } if *attribute == attr)) + } + + /// Helper to check if any field belongs to a specific team (either team-level or player-level). + pub(crate) fn has_team(&self, tid: &str) -> bool { + self.fields.iter().any(|f| match f { + DataField::TrackedEntity { + entity: TrackedEntity::Team { team_id }, + .. + } => team_id == tid, + DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id, .. }, + .. + } => team_id == tid, + _ => false, + }) + } + + /// Helper to check for a specific team-level attribute. + pub(crate) fn has_team_attr(&self, tid: &str, attr: TrackedEntityAttribute) -> bool { + self.fields.iter().any(|f| matches!(f, DataField::TrackedEntity { entity: TrackedEntity::Team { team_id }, attribute } if team_id == tid && *attribute == attr)) + } + + /// Helper to verify that at least one player for a team has required spatial data. + pub(crate) fn has_any_player(&self, tid: &str) -> bool { + let mut players_with_x = std::collections::HashSet::new(); + let mut players_with_y = std::collections::HashSet::new(); + + for f in &self.fields { + if let DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id, player_id }, + attribute, + } = f + && team_id == tid + { + if *attribute == TrackedEntityAttribute::X { + players_with_x.insert(player_id); + } + if *attribute == TrackedEntityAttribute::Y { + players_with_y.insert(player_id); + } + } + } + + !players_with_x.is_empty() + && !players_with_y.is_empty() + && players_with_x + .intersection(&players_with_y) + .next() + .is_some() + } + + /// Returns true if any player has landmark data. + pub(crate) fn has_any_landmark(&self) -> bool { + self.fields.iter().any(|f| { + matches!( + f, + DataField::TrackedEntity { + attribute: TrackedEntityAttribute::LandmarkX(_) + | TrackedEntityAttribute::LandmarkY(_) + | TrackedEntityAttribute::LandmarkZ(_), + .. + } + ) + }) + } +} diff --git a/crates/tdc-core/src/schema/mod.rs b/crates/tdc-core/src/schema/mod.rs new file mode 100644 index 0000000..8ff14c9 --- /dev/null +++ b/crates/tdc-core/src/schema/mod.rs @@ -0,0 +1,17 @@ +pub mod attribute; +pub mod coordinate_system; +pub mod definition; +pub mod entity; +pub mod field; +pub mod fields; +pub mod period; +pub mod registry; + +pub use attribute::{Attribute, FrameAttribute, TrackedEntityAttribute}; +pub use coordinate_system::{CoordinateSystem, Direction, LengthUnit, OrientationMode, Origin}; +pub use definition::{SchemaDefinition, SchemaId}; +pub use entity::TrackedEntity; +pub use field::DataField; +pub use fields::Fields; +pub use period::Period; +pub use registry::{default_schema_id, get_schema, list_schemas}; diff --git a/crates/tdc-core/src/schema/period.rs b/crates/tdc-core/src/schema/period.rs new file mode 100644 index 0000000..7f9b0f1 --- /dev/null +++ b/crates/tdc-core/src/schema/period.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; +use strum_macros::{AsRefStr, Display, EnumString}; + +/// Represents the different periods of a match. +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, AsRefStr, Display, +)] +#[strum(serialize_all = "snake_case")] +pub enum Period { + /// The first half of a regular match. + FirstHalf, + /// The second half of a regular match. + SecondHalf, + /// The first half of extra time. + FirstHalfExtratime, + /// The second half of extra time. + SecondHalfExtratime, + /// Penalty shootout. + Shootout, +} diff --git a/crates/tdc-core/src/schema/registry.rs b/crates/tdc-core/src/schema/registry.rs new file mode 100644 index 0000000..9647670 --- /dev/null +++ b/crates/tdc-core/src/schema/registry.rs @@ -0,0 +1,314 @@ +//! Compile-time registry of known schema definitions. +//! +//! This module provides a global registry of all built-in schema definitions, +//! indexed by their `SchemaId`. New schemas can be added by defining them here. +//! Current schemas are based on the [CDF v0.2.3 specification](https://arxiv.org/pdf/2505.15820). + +use super::definition::{EntityRequirement, SchemaDefinition, SchemaId, ValueRequirement}; +use super::{FrameAttribute, TrackedEntityAttribute}; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +/// Global registry of all known schema definitions. +static REGISTRY: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + + let tracking = cdf_tracking_v0_2_3(); + m.insert(tracking.id.clone(), tracking); + + let skeletal = cdf_skeletal_v0_2_3(); + m.insert(skeletal.id.clone(), skeletal); + + m +}); + +/// Looks up a schema definition by ID. +pub fn get_schema(id: &SchemaId) -> Option<&'static SchemaDefinition> { + REGISTRY.get(id) +} + +/// Returns all registered schema IDs. +pub fn list_schemas() -> Vec<&'static SchemaId> { + REGISTRY.keys().collect() +} + +/// The default schema ID: CDF v0.2.3 tracking data. +pub fn default_schema_id() -> SchemaId { + SchemaId::new("cdf", "tracking", "0.2.3") +} + +// --------------------------------------------------------------------------- +// Built-in schema definitions +// --------------------------------------------------------------------------- + +/// CDF v0.2.3 tracking data. +/// +/// Based on +/// +/// Required top-level: frame_id, timestamp, period, match (with id), teams (home & away), ball (x, y, z). +/// Each team requires: id, players (each with id, x, y). +pub fn cdf_tracking_v0_2_3() -> SchemaDefinition { + SchemaDefinition { + id: SchemaId::new("cdf", "tracking", "0.2.3"), + description: "CDF v0.2.3 tracking data".to_string(), + required_frame_attributes: vec![ + FrameAttribute::FrameId, + FrameAttribute::Timestamp, + FrameAttribute::Period, + FrameAttribute::MatchId, + ], + required_ball_attributes: vec![ + TrackedEntityAttribute::X, + TrackedEntityAttribute::Y, + TrackedEntityAttribute::Z, + ], + entity_requirements: vec![ + // teams.home required + EntityRequirement { + check: |f| f.has_team("home"), + error_field: "teams/home".to_string(), + }, + // teams.home.id required + EntityRequirement { + check: |f| { + if f.has_team("home") { + f.has_team_attr("home", TrackedEntityAttribute::Id) + } else { + true + } + }, + error_field: "teams/home/id".to_string(), + }, + // teams.home.players required (at least one) + EntityRequirement { + check: |f| { + if f.has_team("home") { + f.has_any_player("home") + } else { + true + } + }, + error_field: "teams/home/players".to_string(), + }, + // teams.away required + EntityRequirement { + check: |f| f.has_team("away"), + error_field: "teams/away".to_string(), + }, + // teams.away.id required + EntityRequirement { + check: |f| { + if f.has_team("away") { + f.has_team_attr("away", TrackedEntityAttribute::Id) + } else { + true + } + }, + error_field: "teams/away/id".to_string(), + }, + // teams.away.players required (at least one) + EntityRequirement { + check: |f| { + if f.has_team("away") { + f.has_any_player("away") + } else { + true + } + }, + error_field: "teams/away/players".to_string(), + }, + // no other teams allowed + EntityRequirement { + check: |f| { + f.teams() + .iter() + .all(|t| t == "home" || t == "away" || t == "none") + }, + error_field: "teams/".to_string(), + }, + ], + value_requirements: vec![ValueRequirement { + column: "period".to_string(), + allowed_values: vec![ + "first_half".to_string(), + "second_half".to_string(), + "first_half_extratime".to_string(), + "second_half_extratime".to_string(), + "shootout".to_string(), + ], + }], + core_columns: vec![ + "match/id", + "competition/id", + "season/id", + "timestamp", + "frame_id", + "period", + "period_id", + "player/id", + "team/id", + "entity_key", + "ball_status", + "ball_poss_team_id", + "ball_poss_status", + ] + .into_iter() + .map(String::from) + .collect(), + } +} + +/// CDF v0.2.3 skeletal tracking data. +/// +/// Based on +/// +/// Required top-level: frame_id, timestamp, period, match (with id), teams (home & away). +/// Ball is optional. Each team requires: id, players (each with id, landmarks). +/// Each landmark requires: index, name, x, y, z, children, is_visible. +pub fn cdf_skeletal_v0_2_3() -> SchemaDefinition { + SchemaDefinition { + id: SchemaId::new("cdf", "skeletal", "0.2.3"), + description: "CDF v0.2.3 skeletal tracking data".to_string(), + required_frame_attributes: vec![ + FrameAttribute::FrameId, + FrameAttribute::Timestamp, + FrameAttribute::Period, + FrameAttribute::MatchId, + ], + required_ball_attributes: vec![], + entity_requirements: vec![ + // teams.home required + EntityRequirement { + check: |f| f.has_team("home"), + error_field: "teams/home".to_string(), + }, + // teams.home.id required + EntityRequirement { + check: |f| { + if f.has_team("home") { + f.has_team_attr("home", TrackedEntityAttribute::Id) + } else { + true + } + }, + error_field: "teams/home/id".to_string(), + }, + // teams.home.players required (at least one) + EntityRequirement { + check: |f| { + if f.has_team("home") { + f.has_any_player("home") + } else { + true + } + }, + error_field: "teams/home/players".to_string(), + }, + // teams.away required + EntityRequirement { + check: |f| f.has_team("away"), + error_field: "teams/away".to_string(), + }, + // teams.away.id required + EntityRequirement { + check: |f| { + if f.has_team("away") { + f.has_team_attr("away", TrackedEntityAttribute::Id) + } else { + true + } + }, + error_field: "teams/away/id".to_string(), + }, + // teams.away.players required (at least one) + EntityRequirement { + check: |f| { + if f.has_team("away") { + f.has_any_player("away") + } else { + true + } + }, + error_field: "teams/away/players".to_string(), + }, + // At least one player must have landmarks + EntityRequirement { + check: |f| f.has_any_landmark(), + error_field: "teams/*/players/*/landmarks".to_string(), + }, + // no other teams allowed + EntityRequirement { + check: |f| { + f.teams() + .iter() + .all(|t| t == "home" || t == "away" || t == "none") + }, + error_field: "teams/".to_string(), + }, + ], + value_requirements: vec![ValueRequirement { + column: "period".to_string(), + allowed_values: vec![ + "first_half".to_string(), + "second_half".to_string(), + "first_half_extratime".to_string(), + "second_half_extratime".to_string(), + "shootout".to_string(), + ], + }], + core_columns: vec![ + "match/id", + "competition/id", + "season/id", + "timestamp", + "frame_id", + "period", + "period_id", + "player/id", + "team/id", + "entity_key", + ] + .into_iter() + .map(String::from) + .collect(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_registry_contains_all_schemas() { + let schemas = list_schemas(); + assert_eq!(schemas.len(), 2); + } + + #[test] + fn test_lookup_cdf_tracking() { + let id = SchemaId::new("cdf", "tracking", "0.2.3"); + let schema = get_schema(&id); + assert!(schema.is_some()); + assert_eq!(schema.unwrap().id, id); + } + + #[test] + fn test_lookup_cdf_skeletal() { + let id = SchemaId::new("cdf", "skeletal", "0.2.3"); + let schema = get_schema(&id); + assert!(schema.is_some()); + assert_eq!(schema.unwrap().id, id); + } + + #[test] + fn test_lookup_unknown_schema() { + let id = SchemaId::new("basketball", "tracking", "1.0.0"); + assert!(get_schema(&id).is_none()); + } + + #[test] + fn test_default_schema_id() { + let id = default_schema_id(); + assert_eq!(id, SchemaId::new("cdf", "tracking", "0.2.3")); + } +} diff --git a/crates/tdc-core/src/selector/expr.rs b/crates/tdc-core/src/selector/expr.rs new file mode 100644 index 0000000..b5b0ab0 --- /dev/null +++ b/crates/tdc-core/src/selector/expr.rs @@ -0,0 +1,152 @@ +use crate::schema::attribute::Attribute; +use crate::schema::{DataField, Fields, TrackedEntity}; + +#[derive(Debug, Clone)] +pub enum Expr { + MatchAll, + Field(DataField), + FieldName(String), + Ball, + Players { + team_id: Option, + }, + Player { + team_id: Option, + player_id: String, + }, + Team { + team_id: String, + }, + Attributes { + inner: Box, + names: Vec, + }, + Wildcard(String), +} + +pub fn expand_expr(container: &impl crate::selector::expr::HasSchema, expr: &Expr) -> Vec { + let schema = container.schema(); + match expr { + Expr::MatchAll => schema.fields.iter().map(|f| f.name()).collect(), + Expr::FieldName(name) => vec![name.clone()], + Expr::Field(field) => vec![field.name()], + Expr::Ball => schema + .fields + .iter() + .filter(|f| { + matches!( + f, + DataField::TrackedEntity { + entity: TrackedEntity::Ball, + .. + } + ) + }) + .map(|f| f.name()) + .collect(), + Expr::Players { team_id } => schema + .fields + .iter() + .filter(|f| { + if let DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id: tid, .. }, + .. + } = f + { + team_id.is_none() || team_id.as_ref() == Some(tid) + } else { + false + } + }) + .map(|f| f.name()) + .collect(), + Expr::Player { team_id, player_id } => schema + .fields + .iter() + .filter(|f| { + if let DataField::TrackedEntity { + entity: + TrackedEntity::Player { + team_id: tid, + player_id: pid, + }, + .. + } = f + { + (team_id.is_none() || team_id.as_ref() == Some(tid)) && player_id == pid + } else { + false + } + }) + .map(|f| f.name()) + .collect(), + Expr::Team { team_id } => schema + .fields + .iter() + .filter(|f| { + if let DataField::TrackedEntity { + entity: TrackedEntity::Team { team_id: tid }, + .. + } = f + { + team_id == tid + } else { + false + } + }) + .map(|f| f.name()) + .collect(), + Expr::Attributes { inner, names } => { + let base = expand_expr(container, inner); + base.into_iter() + .filter(|name| { + let field = DataField::from_name(name); + match &field { + DataField::Frame { attribute } => { + names.contains(&attribute.name().to_string()) + } + DataField::TrackedEntity { attribute, .. } => { + names.contains(&attribute.name().to_string()) + } + } + }) + .collect() + } + Expr::Wildcard(pattern) => { + let mut result = std::collections::HashSet::new(); + + // Truly robust glob-to-regex + let mut regex_str = pattern.replace(".", r"\."); + + // Replace ** with a special marker first + regex_str = regex_str.replace("/**/", "___DOUBLE_STAR_MID___"); + regex_str = regex_str.replace("**/", "___DOUBLE_STAR_START___"); + regex_str = regex_str.replace("/**", "___DOUBLE_STAR_END___"); + regex_str = regex_str.replace("**", "___DOUBLE_STAR_ANY___"); + + // Replace * + regex_str = regex_str.replace("*", "[^/]*"); + + // Restore markers with regex equivalents + regex_str = regex_str.replace("___DOUBLE_STAR_MID___", "/(?:.*/)?"); + regex_str = regex_str.replace("___DOUBLE_STAR_START___", "(?:.*/)?"); + regex_str = regex_str.replace("___DOUBLE_STAR_END___", "(?:/.*)?"); + regex_str = regex_str.replace("___DOUBLE_STAR_ANY___", ".*"); + + let re = regex::Regex::new(&format!("^{}$", regex_str)).unwrap(); + for field in &schema.fields { + let name = field.name(); + if re.is_match(&name) { + result.insert(name); + } + } + let mut final_list: Vec = result.into_iter().collect(); + final_list.sort(); + final_list + } + } +} + +pub trait HasSchema { + fn schema(&self) -> &Fields; +} diff --git a/crates/tdc-core/src/selector/mod.rs b/crates/tdc-core/src/selector/mod.rs new file mode 100644 index 0000000..3a92afa --- /dev/null +++ b/crates/tdc-core/src/selector/mod.rs @@ -0,0 +1,19 @@ +pub mod expr; + +use crate::error::TrackingDataContainerResult; +use crate::schema::DataField; + +pub trait Select: expr::HasSchema + Sized { + /// Selects a subset of columns from the container. + fn select(&self, fields: Vec) -> TrackingDataContainerResult; + + /// Selects columns based on a selection expression. + fn select_expr(&self, expr: &expr::Expr) -> TrackingDataContainerResult { + let field_names = expr::expand_expr(self, expr); + let fields = field_names + .into_iter() + .map(|name| DataField::from_name(&name)) + .collect(); + self.select(fields) + } +} diff --git a/crates/tdc-core/tests/test_builder.rs b/crates/tdc-core/tests/test_builder.rs new file mode 100644 index 0000000..e50a43c --- /dev/null +++ b/crates/tdc-core/tests/test_builder.rs @@ -0,0 +1,84 @@ +use arrow_array::Array; +use arrow_schema::DataType; +use tdc_core::TrackingDataBuilder; + +#[test] +fn test_builder_basic_ingestion() { + let mut builder = TrackingDataBuilder::new(); + builder.insert_int(1, "period", 1); + builder.insert_float(1, "ball/x", 10.5); + builder.insert_float(2, "ball/x", 20.0); + builder.insert_int(2, "period", 1); + + let container = builder.build().unwrap(); + assert_eq!(container.num_rows(), 2); + + // Check columns + let fields = container.data.fields(); + assert!(fields.iter().any(|f| f.name() == "frame_id")); + assert!(fields.iter().any(|f| f.name() == "period")); + assert!(fields.iter().any(|f| f.name() == "ball/x")); +} + +#[test] +fn test_builder_dictionary_encoding() { + let mut builder = TrackingDataBuilder::new(); + // "period" is marked as low-cardinality in our schema + builder.insert_string(1, "period", "first_half"); + builder.insert_string(2, "period", "first_half"); + builder.insert_string(3, "period", "second_half"); + + let container = builder.build().unwrap(); + let col_idx = container + .data + .fields() + .iter() + .position(|f| f.name() == "period") + .unwrap(); + let array = container.data.column(col_idx); + + assert!(matches!(array.data_type(), DataType::Dictionary(_, _))); +} + +#[test] +fn test_builder_merge() { + let mut builder1 = TrackingDataBuilder::new(); + builder1.insert_float(1, "ball/x", 1.0); + + let mut builder2 = TrackingDataBuilder::new(); + builder2.insert_float(2, "ball/x", 2.0); + + builder1.merge(builder2); + let container = builder1.build().unwrap(); + + assert_eq!(container.num_rows(), 2); +} + +#[test] +fn test_builder_out_of_order() { + let mut builder = TrackingDataBuilder::new(); + builder.insert_float(10, "ball/x", 10.0); + builder.insert_float(1, "ball/x", 1.0); + + let container = builder.build().unwrap(); + assert_eq!(container.num_rows(), 2); +} + +#[test] +fn test_builder_type_conversion() { + let mut builder = TrackingDataBuilder::new(); + let col = "teams/home/players/1/vel"; // Defaults to Float32 + builder.insert_int(1, col, 10); + builder.insert_float(2, col, 20.5); + + let container = builder.build().unwrap(); + let col_idx = container + .data + .fields() + .iter() + .position(|f| f.name() == col) + .unwrap(); + let array = container.data.column(col_idx); + + assert_eq!(array.data_type(), &DataType::Float32); +} diff --git a/crates/tdc-core/tests/test_datacontainer.rs b/crates/tdc-core/tests/test_datacontainer.rs new file mode 100644 index 0000000..ab27749 --- /dev/null +++ b/crates/tdc-core/tests/test_datacontainer.rs @@ -0,0 +1,308 @@ +use arrow_array::{Float32Array, Float64Array}; +use datafusion::prelude::*; +use std::sync::Arc; +use tdc_core::{ + CoordinateSystem, DataField, Direction, LengthUnit, OrientationMode, Origin, Select, + TrackingDataBuilder, +}; + +fn create_test_container() -> tdc_core::TrackingDataContainer { + let mut builder = TrackingDataBuilder::new(); + builder.insert_float(1, "ball/x", 10.0); + builder.insert_float(1, "ball/y", 20.0); + builder.insert_string(1, "period", "first_half"); + builder.insert_string(1, "ball_poss_team_id", "home"); + + builder.insert_float(2, "ball/x", -10.0); + builder.insert_float(2, "ball/y", -20.0); + builder.insert_string(2, "period", "second_half"); + builder.insert_string(2, "ball_poss_team_id", "away"); + + builder.build().unwrap() +} + +#[test] +fn test_container_basics() { + let container = create_test_container(); + assert_eq!(container.num_rows(), 2); + assert!(!container.is_empty()); +} + +#[test] +fn test_container_select() { + let container = create_test_container(); + let selected = container + .select(vec![ + DataField::from_name("ball/x"), + DataField::from_name("period"), + ]) + .unwrap(); + + assert_eq!(selected.num_rows(), 2); + assert_eq!(selected.schema.fields.len(), 2); + assert_eq!(selected.schema.fields[0].name(), "ball/x"); + assert_eq!(selected.schema.fields[1].name(), "period"); +} + +#[test] +fn test_container_with_column() { + let container = create_test_container(); + let new_data: Arc = Arc::new(Float64Array::from(vec![1.0, 2.0])); + let updated = container.with_columns(vec![DataField::from_name("custom/val")], vec![new_data]); + + assert_eq!( + updated.schema.fields.len(), + container.schema.fields.len() + 1 + ); + assert!( + updated + .schema + .fields + .iter() + .any(|f: &DataField| f.name() == "custom/val") + ); +} + +#[test] +fn test_container_with_expr_column() { + let container = create_test_container(); + let ctx = SessionContext::new(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + // Add a column that is ball/x * 2 + let updated = container + .with_expr_columns_ctx( + vec!["ball/x_double".to_string()], + vec![col("ball/x").mul(lit(2.0))], + &ctx, + rt.handle(), + ) + .unwrap(); + + let idx = updated + .schema + .fields + .iter() + .position(|f: &DataField| f.name() == "ball/x_double") + .unwrap(); + let array = updated + .data + .column(idx) + .as_any() + .downcast_ref::() + .unwrap(); + + assert_eq!(array.value(0), 20.0); + assert_eq!(array.value(1), -20.0); +} + +#[test] +fn test_container_transform_coordinates() { + let mut container = create_test_container(); + + // Define a source system (Center origin, 105x68 meters) + let source_cs = CoordinateSystem::new( + Some((-52.5, 52.5)), + Some((-34.0, 34.0)), + Origin::Center, + Direction::Right, + Direction::Up, + Some(LengthUnit::Meters), + ); + container = container.with_coordinate_system(source_cs); + + // Define a target system (TopLeft origin, same dimensions) + let target_cs = CoordinateSystem::new( + Some((0.0, 105.0)), + Some((0.0, 68.0)), + Origin::TopLeft, + Direction::Right, + Direction::Down, + Some(LengthUnit::Meters), + ); + + let transformed = container.transform_coordinates(target_cs).unwrap(); + + // ball/x: 10.0 in center-based (-52.5 to 52.5) should be 52.5 + 10.0 = 62.5 in TL-based + let x_idx = transformed + .schema + .fields + .iter() + .position(|f| f.name() == "ball/x") + .unwrap(); + let x_array = transformed + .data + .column(x_idx) + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!(x_array.value(0), 62.5f32); + + // ball/y: 20.0 in center-based (Up is positive) should be 34.0 - 20.0 = 14.0 in TL-based (Down is positive) + let y_idx = transformed + .schema + .fields + .iter() + .position(|f| f.name() == "ball/y") + .unwrap(); + let y_array = transformed + .data + .column(y_idx) + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!(y_array.value(0), 14.0f32); +} + +#[test] +fn test_container_transform_orientation() { + let container = create_test_container().with_orientation(OrientationMode::StaticHomeAway); + assert_eq!(container.orientation, Some(OrientationMode::StaticHomeAway)); + + // HomeAway: flips second half + let oriented = container + .transform_orientation(OrientationMode::HomeAway) + .unwrap(); + assert_eq!(oriented.orientation, Some(OrientationMode::HomeAway)); + + let x_idx = oriented + .schema + .fields + .iter() + .position(|f| f.name() == "ball/x") + .unwrap(); + let x_array = oriented + .data + .column(x_idx) + .as_any() + .downcast_ref::() + .unwrap(); + + // Frame 1 (first half): x=10.0 -> remains 10.0 + assert_eq!(x_array.value(0), 10.0f32); + // Frame 2 (second half): x=-10.0 -> flipped to 10.0 + assert_eq!(x_array.value(1), 10.0f32); + + // BallOwningTeam: flips when away team has possession + let oriented_poss = container + .transform_orientation(OrientationMode::BallOwningTeam) + .unwrap(); + assert_eq!( + oriented_poss.orientation, + Some(OrientationMode::BallOwningTeam) + ); + let x_array_poss = oriented_poss + .data + .column(x_idx) + .as_any() + .downcast_ref::() + .unwrap(); + + // Frame 1 (home poss): x=10.0 -> remains 10.0 + assert_eq!(x_array_poss.value(0), 10.0f32); + // Frame 2 (away poss): x=-10.0 -> flipped to 10.0 + assert_eq!(x_array_poss.value(1), 10.0f32); +} + +#[test] +fn test_container_transform_orientation_double() { + let container = create_test_container().with_orientation(OrientationMode::StaticHomeAway); + + // First transform: to AwayHome (flips first half) + let oriented1 = container + .transform_orientation(OrientationMode::AwayHome) + .unwrap(); + let x_idx = oriented1 + .schema + .fields + .iter() + .position(|f| f.name() == "ball/x") + .unwrap(); + let x_array1 = oriented1 + .data + .column(x_idx) + .as_any() + .downcast_ref::() + .unwrap(); + + // Frame 1 (first half): x=10.0 -> flipped to -10.0 + assert_eq!(x_array1.value(0), -10.0f32); + // Frame 2 (second half): x=-10.0 -> remains -10.0 + assert_eq!(x_array1.value(1), -10.0f32); + + // Second transform: from AwayHome to HomeAway (flips second half, un-flips first half) + let oriented2 = oriented1 + .transform_orientation(OrientationMode::HomeAway) + .unwrap(); + let x_array2 = oriented2 + .data + .column(x_idx) + .as_any() + .downcast_ref::() + .unwrap(); + + // Frame 1 (first half): was -10.0 -> should be 10.0 again + assert_eq!(x_array2.value(0), 10.0f32); + // Frame 2 (second half): was -10.0 -> should be 10.0 (flipped from original -10.0) + assert_eq!(x_array2.value(1), 10.0f32); +} + +#[test] +fn test_container_transform_orientation_fails_without_orientation() { + let mut builder = TrackingDataBuilder::new(); + builder.insert_float(1, "ball/x", 10.0); + builder.insert_string(1, "period", "first_half"); + let container = builder.build().unwrap(); + assert_eq!(container.orientation, None); + + let result = container.transform_orientation(OrientationMode::AwayHome); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid parameters: No orientation" + ); +} + +#[test] +fn test_container_with_orientation() { + let container = create_test_container(); + let updated = container.with_orientation(OrientationMode::StaticHomeAway); + assert_eq!(updated.orientation, Some(OrientationMode::StaticHomeAway)); +} + +#[test] +fn test_container_transform_coordinates_fails_without_system() { + let container = create_test_container(); + let target_cs = CoordinateSystem::new( + None, + None, + Origin::Center, + Direction::Right, + Direction::Up, + None, + ); + let result = container.transform_coordinates(target_cs); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid parameters: No coordinate system" + ); +} + +#[test] +fn test_container_transform_orientation_fails_without_period() { + let container = create_test_container().with_orientation(OrientationMode::StaticHomeAway); + // Select only ball/x to remove the period column + let container_no_period = container + .select(vec![DataField::from_name("ball/x")]) + .unwrap(); + + let result = container_no_period.transform_orientation(OrientationMode::HomeAway); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "schema error: Missing period column" + ); +} diff --git a/crates/tdc-core/tests/test_fields.rs b/crates/tdc-core/tests/test_fields.rs new file mode 100644 index 0000000..6d783ac --- /dev/null +++ b/crates/tdc-core/tests/test_fields.rs @@ -0,0 +1,54 @@ +use tdc_core::schema::{Fields, TrackedEntity}; + +#[test] +fn test_fields_entities() { + let cols = vec![ + "frame_id".to_string(), + "teams/home/players/1/x".to_string(), + "teams/home/players/2/x".to_string(), + "teams/away/players/10/x".to_string(), + "teams/home/id".to_string(), + "teams/away/formation".to_string(), + "referees/ref_1/x".to_string(), + "ball/x".to_string(), + ]; + let fields = Fields::new(cols); + + // Test players + let players = fields.players(None); + assert_eq!(players.len(), 3); + assert!(players.contains(&"1".to_string())); + assert!(players.contains(&"2".to_string())); + assert!(players.contains(&"10".to_string())); + + let home_players = fields.players(Some("home")); + assert_eq!(home_players.len(), 2); + assert!(home_players.contains(&"1".to_string())); + assert!(home_players.contains(&"2".to_string())); + + // Test teams + let teams = fields.teams(); + assert_eq!(teams.len(), 2); + assert!(teams.contains(&"home".to_string())); + assert!(teams.contains(&"away".to_string())); + + let team_entities = fields.team_entities(); + assert_eq!(team_entities.len(), 2); + assert!(team_entities.contains(&TrackedEntity::Team { + team_id: "home".to_string() + })); + assert!(team_entities.contains(&TrackedEntity::Team { + team_id: "away".to_string() + })); + + // Test referees + let referees = fields.referees(); + assert_eq!(referees.len(), 1); + assert!(referees.contains(&"ref_1".to_string())); + + let referee_entities = fields.referee_entities(); + assert_eq!(referee_entities.len(), 1); + assert!(referee_entities.contains(&TrackedEntity::Referee { + referee_id: "ref_1".to_string() + })); +} diff --git a/crates/tdc-core/tests/test_schema.rs b/crates/tdc-core/tests/test_schema.rs new file mode 100644 index 0000000..b0a78f8 --- /dev/null +++ b/crates/tdc-core/tests/test_schema.rs @@ -0,0 +1,142 @@ +use arrow_schema::{DataType, TimeUnit}; +use std::str::FromStr; +use tdc_core::{DataField, FrameAttribute, TrackedEntity, TrackedEntityAttribute}; + +#[test] +fn test_data_field_from_name() { + // 1. Frame attributes + assert_eq!( + DataField::from_name("timestamp"), + DataField::Frame { + attribute: FrameAttribute::Timestamp + } + ); + assert_eq!( + DataField::from_name("match/id"), + DataField::Frame { + attribute: FrameAttribute::MatchId + } + ); + assert_eq!( + DataField::from_name("vendor/tracking/name"), + DataField::Frame { + attribute: FrameAttribute::VendorTrackingName + } + ); + + // 2. Tracked Entity: Player + let field = DataField::from_name("teams/home/players/1/x"); + match field { + DataField::TrackedEntity { entity, attribute } => { + assert!( + matches!(entity, TrackedEntity::Player { team_id, player_id } if team_id == "home" && player_id == "1") + ); + assert_eq!(attribute, TrackedEntityAttribute::X); + } + _ => panic!("Expected Player attribute"), + } + + // 3. Tracked Entity: Ball + let field = DataField::from_name("ball/z"); + match field { + DataField::TrackedEntity { entity, attribute } => { + assert!(matches!(entity, TrackedEntity::Ball)); + assert_eq!(attribute, TrackedEntityAttribute::Z); + } + _ => panic!("Expected Ball attribute"), + } + + // 4. Tracked Entity: Referee + let field = DataField::from_name("referees/REF_1/y"); + match field { + DataField::TrackedEntity { entity, attribute } => { + assert!( + matches!(entity, TrackedEntity::Referee { referee_id } if referee_id == "REF_1") + ); + assert_eq!(attribute, TrackedEntityAttribute::Y); + } + _ => panic!("Expected Referee attribute"), + } + + // 5. Tracked Entity: Team + let field = DataField::from_name("teams/away/jersey_colour"); + match field { + DataField::TrackedEntity { entity, attribute } => { + assert!(matches!(entity, TrackedEntity::Team { team_id } if team_id == "away")); + assert_eq!(attribute, TrackedEntityAttribute::JerseyColour); + } + _ => panic!("Expected Team attribute"), + } + + // 6. Custom attributes + assert_eq!( + DataField::from_name("ball/spin"), + DataField::TrackedEntity { + entity: TrackedEntity::Ball, + attribute: TrackedEntityAttribute::Other("spin".to_string()) + } + ); + assert_eq!( + DataField::from_name("global_weather"), + DataField::Frame { + attribute: FrameAttribute::Other("global_weather".to_string()) + } + ); +} + +#[test] +fn test_field_name_serialization() { + let cases = vec![ + "teams/home/players/10/x", + "ball/z", + "referees/ref1/vel", + "vendor/tracking/name", + "match/id", + "timestamp", + ]; + for path in cases { + assert_eq!(DataField::from_name(path).name(), path); + } +} + +#[test] +fn test_field_properties() { + // Arrow types + assert_eq!( + DataField::from_name("timestamp").default_arrow_type(), + DataType::Timestamp(TimeUnit::Microsecond, None) + ); + assert_eq!( + DataField::from_name("ball/x").default_arrow_type(), + DataType::Float32 + ); + assert_eq!( + DataField::from_name("ball_status").default_arrow_type(), + DataType::Boolean + ); + + // Cardinality + assert!(DataField::from_name("period").is_low_cardinality()); + assert!(DataField::from_name("teams/home/jersey_colour").is_low_cardinality()); + assert!(!DataField::from_name("ball/x").is_low_cardinality()); +} + +#[test] +fn test_attribute_enums() { + // FrameAttribute parsing + assert_eq!( + FrameAttribute::from_str("timestamp").unwrap(), + FrameAttribute::Timestamp + ); + assert_eq!( + FrameAttribute::from_str("match").unwrap(), + FrameAttribute::MatchId + ); + + // TrackedEntityAttribute display + assert_eq!(TrackedEntityAttribute::X.to_string(), "x"); + assert_eq!( + TrackedEntityAttribute::JerseyColour.to_string(), + "jersey_colour" + ); +} diff --git a/crates/tdc-ops/Cargo.toml b/crates/tdc-ops/Cargo.toml new file mode 100644 index 0000000..8757d45 --- /dev/null +++ b/crates/tdc-ops/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tdc-ops" +version = "0.1.0-alpha.1" +edition = "2024" +license = "MIT" +description = "Vectorized operations for sports tracking data." + +[lib] +name = "tdc_ops" +crate-type = ["lib"] + +[dependencies] +arrow-array = "54" +arrow-schema = "54" +arrow-cast = "54" +arrow-arith = "54" +arrow-select = "54" +arrow-ord = "54" +pyo3 = { version = "0.23", features = ["abi3-py310"] } +pyo3-arrow = { version = "0.7", default-features = false } diff --git a/crates/tdc-ops/src/lib.rs b/crates/tdc-ops/src/lib.rs new file mode 100644 index 0000000..ec46f54 --- /dev/null +++ b/crates/tdc-ops/src/lib.rs @@ -0,0 +1,277 @@ +use arrow_arith::numeric::{add, mul, sub}; +use arrow_array::{Array, ArrayRef, Float64Array, Scalar}; +use arrow_cast::cast; +use arrow_schema::{ArrowError, DataType}; +use pyo3::IntoPyObjectExt; +use pyo3::prelude::*; +use pyo3::types::PyCapsule; +use std::sync::Arc; + +fn extract_array(obj: &Bound<'_, PyAny>) -> PyResult { + let (array_cap, schema_capsule) = if let Ok(caps) = obj.call_method0("__arrow_c_array__") { + caps.extract::<(Bound<'_, PyCapsule>, Bound<'_, PyCapsule>)>()? + } else { + return Err(pyo3::exceptions::PyTypeError::new_err( + "Expected object with __arrow_c_array__", + )); + }; + + let py_array = pyo3_arrow::PyArray::from_arrow_pycapsule(&array_cap, &schema_capsule)?; + Ok(py_array.into_inner().0) +} + +/// Helper to compute the difference between consecutive elements: values[i] - values[i-1] +/// The first element of the result is 0.0. +fn compute_diff(array: &ArrayRef) -> Result { + let len = array.len(); + if len <= 1 { + return Ok(Arc::new(Float64Array::from(vec![0.0; len])) as ArrayRef); + } + + // array[1..len] + let current = array.slice(1, len - 1); + // array[0..len-1] + let previous = array.slice(0, len - 1); + + let diff = sub(¤t, &previous)?; + + // Prepend 0.0 to match original length + let zero = Float64Array::from(vec![0.0]); + arrow_select::concat::concat(&[&zero, &diff]) +} + +#[pyfunction] +#[pyo3(signature = (x_array, y_array, dt=1.0))] +pub fn compute_speed( + py: Python<'_>, + x_array: Bound<'_, PyAny>, + y_array: Bound<'_, PyAny>, + dt: f64, +) -> PyResult { + let x_arr = extract_array(&x_array)?; + let y_arr = extract_array(&y_array)?; + + let x_f64 = cast(&x_arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let y_f64 = cast(&y_arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let dx = compute_diff(&x_f64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let dy = compute_diff(&y_f64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + // dist2 = dx^2 + dy^2 + let dx2 = mul(&dx, &dx) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let dy2 = mul(&dy, &dy) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let dist2_arr = add(&dx2, &dy2) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let dist2_f64 = dist2_arr.as_any().downcast_ref::().unwrap(); + let dist_values: Vec = dist2_f64.values().iter().map(|&v| v.sqrt()).collect(); + let dist = Arc::new(Float64Array::from(dist_values)) as ArrayRef; + + // speed = dist / dt + let inv_dt = Scalar::new(Float64Array::from(vec![1.0 / dt])); + let speed = mul(&dist, &inv_dt) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + Ok(pyo3_arrow::PyArray::new( + speed, + Arc::new(arrow_schema::Field::new("speed", DataType::Float64, true)), + ) + .into_bound_py_any(py)? + .into()) +} + +#[pyfunction] +#[pyo3(signature = (speed_array, dt=1.0))] +pub fn compute_acceleration( + py: Python<'_>, + speed_array: Bound<'_, PyAny>, + dt: f64, +) -> PyResult { + let arr = extract_array(&speed_array)?; + let f64_arr = cast(&arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let dv = compute_diff(&f64_arr) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let inv_dt = Scalar::new(Float64Array::from(vec![1.0 / dt])); + let acc = mul(&dv, &inv_dt) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + Ok(pyo3_arrow::PyArray::new( + acc, + Arc::new(arrow_schema::Field::new( + "acceleration", + DataType::Float64, + true, + )), + ) + .into_bound_py_any(py)? + .into()) +} + +#[pyfunction] +#[pyo3(signature = (acc_array, dt=1.0))] +pub fn compute_jerk(py: Python<'_>, acc_array: Bound<'_, PyAny>, dt: f64) -> PyResult { + let arr = extract_array(&acc_array)?; + let f64_arr = cast(&arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let da = compute_diff(&f64_arr) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let inv_dt = Scalar::new(Float64Array::from(vec![1.0 / dt])); + let jerk = mul(&da, &inv_dt) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + Ok(pyo3_arrow::PyArray::new( + jerk, + Arc::new(arrow_schema::Field::new("jerk", DataType::Float64, true)), + ) + .into_bound_py_any(py)? + .into()) +} + +#[pyfunction] +#[pyo3(signature = (vx_array, vy_array, dt=1.0))] +pub fn compute_angular_velocity( + py: Python<'_>, + vx_array: Bound<'_, PyAny>, + vy_array: Bound<'_, PyAny>, + dt: f64, +) -> PyResult { + let vx_arr = extract_array(&vx_array)?; + let vy_arr = extract_array(&vy_array)?; + + let vx_f64 = cast(&vx_arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let vx_f64 = vx_f64.as_any().downcast_ref::().unwrap(); + + let vy_f64 = cast(&vy_arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let vy_f64 = vy_f64.as_any().downcast_ref::().unwrap(); + + let len = vx_f64.len(); + let mut values = vec![0.0; len]; + let (vxs, vys) = (vx_f64.values(), vy_f64.values()); + let inv_dt = 1.0 / dt; + + for i in 1..len { + let theta_prev = vys[i - 1].atan2(vxs[i - 1]); + let theta_curr = vys[i].atan2(vxs[i]); + let mut d_theta = theta_curr - theta_prev; + + // Handle wrap-around + if d_theta > std::f64::consts::PI { + d_theta -= 2.0 * std::f64::consts::PI; + } else if d_theta < -std::f64::consts::PI { + d_theta += 2.0 * std::f64::consts::PI; + } + + values[i] = d_theta * inv_dt; + } + + let result = Arc::new(Float64Array::from(values)) as Arc; + Ok(pyo3_arrow::PyArray::new( + result, + Arc::new(arrow_schema::Field::new( + "angular_velocity", + DataType::Float64, + true, + )), + ) + .into_bound_py_any(py)? + .into()) +} + +#[pyfunction] +#[pyo3(signature = (array, cutoff, fs, order=2))] +pub fn butterworth_filter( + py: Python<'_>, + array: Bound<'_, PyAny>, + cutoff: f64, + fs: f64, + order: usize, +) -> PyResult { + let arr = extract_array(&array)?; + let f64_arr = cast(&arr, &DataType::Float64) + .map_err(|e: ArrowError| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + let f64_arr = f64_arr.as_any().downcast_ref::().unwrap(); + + let input = f64_arr.values(); + let len = input.len(); + if len == 0 { + return Ok(pyo3_arrow::PyArray::new( + Arc::new(Float64Array::from(Vec::::new())) as Arc, + Arc::new(arrow_schema::Field::new( + "filtered", + DataType::Float64, + true, + )), + ) + .into_bound_py_any(py)? + .into()); + } + + // Only 2nd order implemented for now as it's the most common for tracking data + if order != 2 { + return Err(pyo3::exceptions::PyValueError::new_err( + "Only order=2 is currently supported for butterworth_filter", + )); + } + + // Precompute coefficients for 2nd order Butterworth + // Ref: https://en.wikipedia.org/wiki/Butterworth_filter + let ff = cutoff / fs; + let ita = 1.0 / (std::f64::consts::PI * ff).tan(); + let q = std::f64::consts::SQRT_2; + let b0 = 1.0 / (1.0 + q * ita + ita * ita); + let b1 = 2.0 * b0; + let b2 = b0; + let a1 = 2.0 * (ita * ita - 1.0) * b0; + let a2 = -(1.0 - q * ita + ita * ita) * b0; + + let mut output = vec![0.0; len]; + + // Initial conditions (assume steady state or zero padding) + output[0] = input[0]; + if len > 1 { + output[1] = input[1]; + } + + for i in 2..len { + output[i] = b0 * input[i] + + b1 * input[i - 1] + + b2 * input[i - 2] + + a1 * output[i - 1] + + a2 * output[i - 2]; + } + + let result = Arc::new(Float64Array::from(output)) as Arc; + Ok(pyo3_arrow::PyArray::new( + result, + Arc::new(arrow_schema::Field::new( + "filtered", + DataType::Float64, + true, + )), + ) + .into_bound_py_any(py)? + .into()) +} + +#[pymodule] +fn tdc_ops(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(compute_speed, m)?)?; + m.add_function(wrap_pyfunction!(compute_acceleration, m)?)?; + m.add_function(wrap_pyfunction!(compute_jerk, m)?)?; + m.add_function(wrap_pyfunction!(compute_angular_velocity, m)?)?; + m.add_function(wrap_pyfunction!(butterworth_filter, m)?)?; + Ok(()) +} diff --git a/crates/tdc-parsers/Cargo.toml b/crates/tdc-parsers/Cargo.toml new file mode 100644 index 0000000..1d02b58 --- /dev/null +++ b/crates/tdc-parsers/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "tdc-parsers" +version = "0.1.0-alpha.1" +edition = "2024" +license = "MIT" +description = "Specialized parsers for sports tracking data formats." + +[lib] +name = "tdc_parsers" +crate-type = ["lib", "cdylib"] + +[dependencies] +tdc-core = { path = "../tdc-core" } +arrow-array = { version = "54" } +arrow-schema = { version = "54" } +quick-xml = "0.37" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +rayon = "1.10" +memmap2 = "0.9" +ahash = { version = "0.8", features = ["serde"] } +fast-float = "0.2" +chrono = { version = "^0.4.42", default-features = false } +pyo3 = { version = "0.23", features = ["abi3-py310"], optional = true } + +[features] +default = [] +python = ["dep:pyo3"] + +[dev-dependencies] +tempfile = "3" +arrow-cast = { version = "54" } +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "parsers" +harness = false + +[[bench]] +name = "memory" +harness = false diff --git a/crates/tdc-parsers/benches/memory.rs b/crates/tdc-parsers/benches/memory.rs new file mode 100644 index 0000000..10fc298 --- /dev/null +++ b/crates/tdc-parsers/benches/memory.rs @@ -0,0 +1,40 @@ +use arrow_array::Array; +use criterion::{Criterion, criterion_group, criterion_main}; +use tdc_parsers::{load_sportec, load_statsperform}; + +fn bench_memory(c: &mut Criterion) { + // 1. Sportec + let sportec_path = "../../data/sportec-tracking.xml"; + let sportec_meta = "../../data/sportec-meta.xml"; + let sportec_container = load_sportec(sportec_path, Some(sportec_meta)).unwrap(); + let sportec_size = sportec_container.data.get_array_memory_size(); + + c.bench_function("memory_usage_sportec", |b| { + b.iter(|| { + // We just report the size once, but the bench helps see if there's + // any weird variance in container creation if we wanted to bench that too. + // For now, we use it as a placeholder to report info. + criterion::black_box(sportec_size) + }) + }); + println!( + "Sportec Container Memory Usage: {:.2} MB", + sportec_size as f64 / 1024.0 / 1024.0 + ); + + // 2. StatsPerform + let statsperform_path = "../../data/statsperform-ma25.txt"; + let statsperform_container = load_statsperform(statsperform_path).unwrap(); + let statsperform_size = statsperform_container.data.get_array_memory_size(); + + c.bench_function("memory_usage_statsperform", |b| { + b.iter(|| criterion::black_box(statsperform_size)) + }); + println!( + "StatsPerform Container Memory Usage: {:.2} MB", + statsperform_size as f64 / 1024.0 / 1024.0 + ); +} + +criterion_group!(benches, bench_memory); +criterion_main!(benches); diff --git a/crates/tdc-parsers/benches/parsers.rs b/crates/tdc-parsers/benches/parsers.rs new file mode 100644 index 0000000..ec091fc --- /dev/null +++ b/crates/tdc-parsers/benches/parsers.rs @@ -0,0 +1,20 @@ +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use tdc_parsers::{load_sportec, load_statsperform}; + +fn bench_parsers(c: &mut Criterion) { + // Sportec Benchmark + let sportec_path = "../../data/sportec-tracking.xml"; + let sportec_meta = "../../data/sportec-meta.xml"; + c.bench_function("parse sportec xml", |b| { + b.iter(|| load_sportec(black_box(sportec_path), Some(black_box(sportec_meta))).unwrap()) + }); + + // StatsPerform Benchmark + let statsperform_path = "../../data/statsperform-ma25.txt"; + c.bench_function("parse statsperform", |b| { + b.iter(|| load_statsperform(black_box(statsperform_path)).unwrap()) + }); +} + +criterion_group!(benches, bench_parsers); +criterion_main!(benches); diff --git a/crates/tdc-parsers/src/hawkeye.rs b/crates/tdc-parsers/src/hawkeye.rs new file mode 100644 index 0000000..562ebf7 --- /dev/null +++ b/crates/tdc-parsers/src/hawkeye.rs @@ -0,0 +1,470 @@ +use ahash::AHashMap; +use arrow_schema::DataType; +use memmap2::Mmap; +use rayon::prelude::*; +use serde::Deserialize; +use std::fs::{self, File}; +use tdc_core::datacontainer::{TrackingDataBuilder, TrackingDataContainer}; +use tdc_core::schema::{CoordinateSystem, Direction, LengthUnit, Origin}; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct HawkEyeBallData<'a> { + details: BallDetails<'a>, + samples: BallSamples, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct BallDetails<'a> { + teams: Vec>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct TeamInfo<'a> { + id: IdInfo<'a>, + _name: &'a str, + home: bool, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct IdInfo<'a> { + he_id: &'a str, + _uefa_id: Option<&'a str>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct BallSamples { + ball: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct BallSample { + pos: [f32; 3], + _time: f32, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct HawkEyeCentroidData<'a> { + details: CentroidDetails<'a>, + samples: CentroidSamples<'a>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct CentroidDetails<'a> { + players: Vec>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct PlayerInfo<'a> { + id: IdInfo<'a>, + team_id: IdInfo<'a>, + jersey_number: &'a str, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct CentroidSamples<'a> { + people: Vec>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct PersonSample<'a> { + person_id: IdInfo<'a>, + centroid: Vec, + role: RoleInfo<'a>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct RoleInfo<'a> { + _id: u32, + name: &'a str, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct CentroidSample { + pos: [f32; 2], + time: f32, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct HawkEyeMetaData { + stadium: StadiumMetaData, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct StadiumMetaData { + pitch_length: f64, + pitch_width: f64, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct HawkEyeJointData<'a> { + samples: JointSamples<'a>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct JointSamples<'a> { + people: Vec>, +} + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +enum JointValue { + Pos([f32; 3]), + Time(f32), +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +struct PersonJointSample<'a> { + person_id: IdInfo<'a>, + joints: Vec>, +} + +pub fn load_hawkeye( + ball_paths: &[String], + centroid_paths: &[String], + joint_paths: Option<&[String]>, + meta_path: Option<&str>, +) -> Result { + if ball_paths.is_empty() { + return Err("No paths provided".to_string()); + } + if ball_paths.len() != centroid_paths.len() { + return Err("Number of ball and centroid files must match".to_string()); + } + if let Some(jp) = joint_paths + && jp.len() != ball_paths.len() + { + return Err("Number of joint files must match ball and centroid files".to_string()); + } + + // 1. Initial Metadata Pass (from first file) + let ball_file = File::open(&ball_paths[0]).map_err(|e| e.to_string())?; + let ball_mmap = unsafe { Mmap::map(&ball_file).map_err(|e| e.to_string())? }; + let ball_data: HawkEyeBallData = + serde_json::from_slice(&ball_mmap).map_err(|e| e.to_string())?; + + let centroid_file = File::open(¢roid_paths[0]).map_err(|e| e.to_string())?; + let centroid_mmap = unsafe { Mmap::map(¢roid_file).map_err(|e| e.to_string())? }; + let centroid_data: HawkEyeCentroidData = + serde_json::from_slice(¢roid_mmap).map_err(|e| e.to_string())?; + + let mut team_map = AHashMap::new(); // heId -> "home" / "away" + for team in ball_data.details.teams { + let role = if team.home { "home" } else { "away" }; + team_map.insert(team.id.he_id.to_string(), role.to_string()); + } + + let mut player_map = AHashMap::new(); // heId -> (role, jersey) + for player in centroid_data.details.players { + if let Some(role) = team_map.get(player.team_id.he_id) { + player_map.insert( + player.id.he_id.to_string(), + (role.clone(), player.jersey_number.to_string()), + ); + } + } + + // 2. Pre-pass: Find total frames and offsets per file + const FREQ: f32 = 25.0; + + let mut lengths = Vec::with_capacity(ball_paths.len()); + for p in ball_paths { + let file = File::open(p).map_err(|e| e.to_string())?; + let mmap = unsafe { Mmap::map(&file).map_err(|e| e.to_string())? }; + let b_data: HawkEyeBallData = serde_json::from_slice(&mmap).map_err(|e| e.to_string())?; + lengths.push(b_data.samples.ball.len()); + } + + let mut offsets = Vec::with_capacity(lengths.len()); + let mut current_offset = 0; + for &l in &lengths { + offsets.push(current_offset); + current_offset += l; + } + + // 3. Batched Parallel Processing of minutes to limit peak memory + let mut builders = Vec::new(); + let indices: Vec = (0..ball_paths.len()).collect(); + + for chunk in indices.chunks(8) { + let chunk_builders: Vec> = chunk + .into_par_iter() + .map(|&idx| -> Result { + let bp = &ball_paths[idx]; + let cp = ¢roid_paths[idx]; + let jp = joint_paths.map(|p| &p[idx]); + let offset = offsets[idx]; + let minute_len = lengths[idx]; + + let b_file = File::open(bp).map_err(|e| e.to_string())?; + let b_mmap = unsafe { Mmap::map(&b_file).map_err(|e| e.to_string())? }; + let b_data: HawkEyeBallData = + serde_json::from_slice(&b_mmap).map_err(|e| e.to_string())?; + + let c_file = File::open(cp).map_err(|e| e.to_string())?; + let c_mmap = unsafe { Mmap::map(&c_file).map_err(|e| e.to_string())? }; + let c_data: HawkEyeCentroidData = + serde_json::from_slice(&c_mmap).map_err(|e| e.to_string())?; + + let period_name = if bp.contains("_1_") { + "first_half" + } else if bp.contains("_2_") { + "second_half" + } else { + "other" + }; + + let mut builder = TrackingDataBuilder::new_fixed( + offset as i64, + (offset + minute_len).saturating_sub(1) as i64, + ); + let c_period = builder.get_or_create_column_id("period", DataType::Utf8); + let c_vendor = + builder.get_or_create_column_id("vendor/tracking/name", DataType::Utf8); + + let c_bx = builder.get_or_create_column_id("ball/x", DataType::Float32); + let c_by = builder.get_or_create_column_id("ball/y", DataType::Float32); + let c_bz = builder.get_or_create_column_id("ball/z", DataType::Float32); + + // Ball samples + for (i, sample) in b_data.samples.ball.iter().enumerate() { + builder.insert_string_at_idx(i, c_period, period_name); + builder.insert_string_at_idx(i, c_vendor, "hawkeye"); + builder.insert_float_at_idx(i, c_bx, sample.pos[0] as f64); + builder.insert_float_at_idx(i, c_by, sample.pos[1] as f64); + builder.insert_float_at_idx(i, c_bz, sample.pos[2] as f64); + } + + // Optimization: Cache Column IDs to avoid massive string formatting and schema lookups + let mut col_cache: AHashMap<(&str, &str, char), usize> = AHashMap::new(); + + // Player samples (centroids) + let mut current_frame = 0; + let mut last_time = -1.0; + + for person in c_data.samples.people { + let sample = match person.centroid.first() { + Some(s) => s, + None => continue, + }; + if (sample.time - last_time).abs() > (1.0 / (2.0 * FREQ)) { + if last_time != -1.0 { + current_frame += 1; + } + last_time = sample.time; + } + + if current_frame >= minute_len { + continue; + } + + if let Some((role, jersey)) = player_map.get(person.person_id.he_id) { + let pid = person.person_id.he_id; + let ix = *col_cache.entry((pid, "centroid", 'x')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!("teams/{}/players/{}/x", role, jersey), + DataType::Float32, + ) + }); + let iy = *col_cache.entry((pid, "centroid", 'y')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!("teams/{}/players/{}/y", role, jersey), + DataType::Float32, + ) + }); + builder.insert_float_at_idx(current_frame, ix, sample.pos[0] as f64); + builder.insert_float_at_idx(current_frame, iy, sample.pos[1] as f64); + } else if person.role.name.contains("Referee") { + let pid = person.person_id.he_id; + let ix = *col_cache.entry((pid, "centroid", 'x')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!("referees/{}/x", pid), + DataType::Float32, + ) + }); + let iy = *col_cache.entry((pid, "centroid", 'y')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!("referees/{}/y", pid), + DataType::Float32, + ) + }); + builder.insert_float_at_idx(current_frame, ix, sample.pos[0] as f64); + builder.insert_float_at_idx(current_frame, iy, sample.pos[1] as f64); + } + } + + // Player samples (joints) + if let Some(j_path) = jp { + let j_file = File::open(j_path).map_err(|e| e.to_string())?; + let j_mmap = unsafe { Mmap::map(&j_file).map_err(|e| e.to_string())? }; + let j_data: HawkEyeJointData = + serde_json::from_slice(&j_mmap).map_err(|e| e.to_string())?; + + let mut current_frame = 0; + let mut last_time = -1.0; + + for person in j_data.samples.people { + let joints_map = match person.joints.first() { + Some(m) => m, + None => continue, + }; + let time = match joints_map.get("time") { + Some(JointValue::Time(t)) => *t, + _ => 0.0, + }; + + if (time - last_time).abs() > (1.0 / (2.0 * FREQ)) { + if last_time != -1.0 { + current_frame += 1; + } + last_time = time; + } + + if current_frame >= minute_len { + continue; + } + + if let Some((role, jersey)) = player_map.get(person.person_id.he_id) { + for (&j_name, val) in joints_map { + if let JointValue::Pos(pos) = val { + let pid = person.person_id.he_id; + + let ix = + *col_cache.entry((pid, j_name, 'x')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!( + "teams/{}/players/{}/landmarks/{}/x", + role, jersey, j_name + ), + DataType::Float32, + ) + }); + let iy = + *col_cache.entry((pid, j_name, 'y')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!( + "teams/{}/players/{}/landmarks/{}/y", + role, jersey, j_name + ), + DataType::Float32, + ) + }); + let iz = + *col_cache.entry((pid, j_name, 'z')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!( + "teams/{}/players/{}/landmarks/{}/z", + role, jersey, j_name + ), + DataType::Float32, + ) + }); + builder.insert_float_at_idx(current_frame, ix, pos[0] as f64); + builder.insert_float_at_idx(current_frame, iy, pos[1] as f64); + builder.insert_float_at_idx(current_frame, iz, pos[2] as f64); + + let ivis = + *col_cache.entry((pid, j_name, 'v')).or_insert_with(|| { + builder.get_or_create_column_id( + &format!( + "teams/{}/players/{}/landmarks/{}/is_visible", + role, jersey, j_name + ), + DataType::Boolean, + ) + }); + builder.insert_bool_at_idx(current_frame, ivis, true); + } + } + } + } + } + + Ok(builder) + }) + .collect(); + + for res in chunk_builders { + builders.push(res?); + } + } + + let mut final_builder = builders + .into_iter() + .reduce(|mut a, b| { + a.merge(b); + a + }) + .ok_or("No data loaded")?; + + final_builder.get_or_create_column_id("match/id", arrow_schema::DataType::Utf8); + final_builder.get_or_create_column_id( + "timestamp", + arrow_schema::DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None), + ); + final_builder.get_or_create_column_id("teams/home/id", arrow_schema::DataType::Utf8); + final_builder.get_or_create_column_id("teams/away/id", arrow_schema::DataType::Utf8); + + let mut container = final_builder.build().map_err(|e| e.to_string())?; + + let mut x_range = None; + let mut y_range = None; + if let Some(path) = meta_path { + let meta_str = fs::read_to_string(path).map_err(|e| e.to_string())?; + let meta_data: HawkEyeMetaData = + serde_json::from_str(&meta_str).map_err(|e| e.to_string())?; + x_range = Some(( + -meta_data.stadium.pitch_length / 2.0, + meta_data.stadium.pitch_length / 2.0, + )); + y_range = Some(( + -meta_data.stadium.pitch_width / 2.0, + meta_data.stadium.pitch_width / 2.0, + )); + } + + let cs = CoordinateSystem::new( + x_range, + y_range, + Origin::Center, + Direction::Right, + Direction::Up, + Some(LengthUnit::Meters), + ); + container = container.with_coordinate_system(cs); + container = + container.with_schema_id(tdc_core::schema::SchemaId::new("cdf", "skeletal", "0.2.3")); + container.validate().map_err(|e| e.to_string())?; + + Ok(container) +} diff --git a/crates/tdc-parsers/src/lib.rs b/crates/tdc-parsers/src/lib.rs new file mode 100644 index 0000000..931c98f --- /dev/null +++ b/crates/tdc-parsers/src/lib.rs @@ -0,0 +1,69 @@ +pub mod hawkeye; +pub mod sportec; +pub mod statsperform; + +pub use hawkeye::load_hawkeye; +pub use sportec::load_sportec; +pub use statsperform::load_statsperform; + +#[cfg(feature = "python")] +pub mod python { + use pyo3::prelude::*; + use pyo3::types::PyCapsule; + use std::ffi::CString; + + #[pyfunction] + #[pyo3(name = "load_sportec", signature = (path, meta_path=None))] + pub fn load_sportec_py( + py: Python<'_>, + path: String, + meta_path: Option, + ) -> PyResult> { + let container = super::load_sportec(&path, meta_path.as_deref()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let name = CString::new("TrackingDataContainer").unwrap(); + PyCapsule::new(py, container, Some(name)) + } + + #[pyfunction] + #[pyo3(name = "load_statsperform")] + pub fn load_statsperform_py(py: Python<'_>, path: String) -> PyResult> { + let container = super::load_statsperform(&path) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let name = CString::new("TrackingDataContainer").unwrap(); + PyCapsule::new(py, container, Some(name)) + } + + #[pyfunction] + #[pyo3( + name = "load_hawkeye", + signature = (ball_paths, centroid_paths, joint_paths=None, meta_path=None) + )] + pub fn load_hawkeye_py( + py: Python<'_>, + ball_paths: Vec, + centroid_paths: Vec, + joint_paths: Option>, + meta_path: Option, + ) -> PyResult> { + let container = super::load_hawkeye( + &ball_paths, + ¢roid_paths, + joint_paths.as_deref(), + meta_path.as_deref(), + ) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?; + + let name = CString::new("TrackingDataContainer").unwrap(); + PyCapsule::new(py, container, Some(name)) + } + + pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(load_sportec_py, m)?)?; + m.add_function(wrap_pyfunction!(load_statsperform_py, m)?)?; + m.add_function(wrap_pyfunction!(load_hawkeye_py, m)?)?; + Ok(()) + } +} diff --git a/crates/tdc-parsers/src/sportec.rs b/crates/tdc-parsers/src/sportec.rs new file mode 100644 index 0000000..820f3aa --- /dev/null +++ b/crates/tdc-parsers/src/sportec.rs @@ -0,0 +1,487 @@ +use ahash::{AHashMap, AHashSet}; +use arrow_schema::DataType; +use quick_xml::events::Event; +use quick_xml::reader::Reader; +use rayon::prelude::*; +use std::fs; +use tdc_core::datacontainer::{TrackingDataBuilder, TrackingDataContainer}; +use tdc_core::schema::{CoordinateSystem, Direction, LengthUnit, Origin}; + +pub fn load_sportec(path: &str, meta_path: Option<&str>) -> Result { + let raw_data = fs::read_to_string(path).map_err(|e| e.to_string())?; + + // 1. Parse Metadata (Pitch dimensions, Teams) + let mut team_map = AHashMap::new(); + let mut role_to_id = AHashMap::new(); + let mut role_to_name = AHashMap::new(); + let mut role_to_color = AHashMap::new(); + let mut pitch_x = None; + let mut pitch_y = None; + if let Some(mp) = meta_path { + let meta_data = fs::read_to_string(mp).map_err(|e| e.to_string())?; + let mut reader = Reader::from_str(&meta_data); + let mut buf = Vec::new(); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) + if e.name().as_ref() == b"Team" => + { + let mut team_id = String::new(); + let mut role = String::new(); + let mut team_name = String::new(); + let mut team_color = String::new(); + for attr in e.attributes().filter_map(|a| a.ok()) { + match attr.key.as_ref() { + b"TeamId" => { + team_id = String::from_utf8_lossy(&attr.value).into_owned() + } + b"Role" => role = String::from_utf8_lossy(&attr.value).into_owned(), + b"TeamName" => { + team_name = String::from_utf8_lossy(&attr.value).into_owned() + } + b"PlayerShirtMainColor" => { + team_color = String::from_utf8_lossy(&attr.value).into_owned() + } + _ => (), + } + } + if !team_id.is_empty() { + let role_mapped = match role.as_str() { + "home" => "home", + "guest" => "away", + _ => "other", + }; + team_map.insert(team_id.clone(), role_mapped.to_string()); + role_to_id.insert(role_mapped.to_string(), team_id); + if !team_name.is_empty() { + role_to_name.insert(role_mapped.to_string(), team_name); + } + if !team_color.is_empty() { + role_to_color.insert(role_mapped.to_string(), team_color); + } + } + } + Ok(Event::Empty(ref e)) if e.name().as_ref() == b"Environment" => { + for attr in e.attributes().filter_map(|a| a.ok()) { + match attr.key.as_ref() { + b"PitchX" => { + pitch_x = std::str::from_utf8(&attr.value) + .ok() + .and_then(|s| s.parse::().ok()); + } + b"PitchY" => { + pitch_y = std::str::from_utf8(&attr.value) + .ok() + .and_then(|s| s.parse::().ok()); + } + _ => (), + } + } + } + Ok(Event::Eof) => break, + _ => (), + } + buf.clear(); + } + } + + let home_id = role_to_id.get("home").cloned(); + let away_id = role_to_id.get("away").cloned(); + let home_name = role_to_name.get("home").cloned(); + let away_name = role_to_name.get("away").cloned(); + let home_color = role_to_color.get("home").cloned(); + let away_color = role_to_color.get("away").cloned(); + + // 2. Discovery Pass: Find all FrameSets, Frame range, and metadata mappings + let mut fs_positions = Vec::new(); + let mut start = 0; + while let Some(pos) = raw_data[start..].find(" = fs_positions + .par_iter() + .map(|&fs_start| { + let mut entities = AHashSet::new(); + let mut frames = AHashMap::new(); // N -> (Timestamp, Period) + + let mut cur_team = String::new(); + let mut cur_person = String::new(); + + let tag_end = raw_data[fs_start..].find(">").unwrap_or(0); + let mut reader = Reader::from_str(&raw_data[fs_start..fs_start + tag_end + 1]); + let mut buf = Vec::new(); + if let Ok(Event::Start(e)) | Ok(Event::Empty(e)) = reader.read_event_into(&mut buf) { + for a in e.attributes().filter_map(|a| a.ok()) { + match a.key.as_ref() { + b"TeamId" => cur_team = String::from_utf8_lossy(&a.value).into_owned(), + b"PersonId" => cur_person = String::from_utf8_lossy(&a.value).into_owned(), + _ => (), + } + } + } + if !cur_team.is_empty() && !cur_person.is_empty() { + entities.insert((cur_team, cur_person)); + } + + let chunk_end = fs_positions + .iter() + .find(|&&p| p > fs_start) + .cloned() + .unwrap_or(raw_data.len()); + let mut reader = Reader::from_str(&raw_data[fs_start..chunk_end]); + let mut buf = Vec::new(); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) + if e.name().as_ref() == b"Frame" => + { + let mut n = -1; + let mut ts = None; + let mut p = None; + for a in e.attributes().filter_map(|a| a.ok()) { + let val = std::str::from_utf8(&a.value).unwrap_or(""); + match a.key.as_ref() { + b"N" => n = val.parse().unwrap_or(-1), + b"T" if !val.is_empty() => { + ts = Some(val.to_string()); + } + b"M" if !val.is_empty() => { + p = Some(val.parse::().unwrap_or(0)); + } + _ => (), + } + } + if n != -1 { + frames.insert(n, (ts, p)); + } + } + Ok(Event::Eof) => break, + _ => (), + } + buf.clear(); + } + (entities, frames) + }) + .collect(); + + let mut global_entities = AHashSet::new(); + let mut global_frames = AHashMap::new(); + for (entities, frames) in discovery_results { + global_entities.extend(entities); + for (n, (ts, p)) in frames { + let entry = global_frames.entry(n).or_insert((None, None)); + if ts.is_some() { + entry.0 = ts; + } + if p.is_some() { + entry.1 = p; + } + } + } + + if global_frames.is_empty() { + return Err("No frames found in Sportec data".to_string()); + } + + let mut raw_sorted_n: Vec = global_frames.keys().cloned().collect(); + raw_sorted_n.sort_unstable(); + + let mut sorted_n = Vec::new(); + let mut valid_periods = Vec::new(); + let mut current_period = "other"; + + for &n in &raw_sorted_n { + let (_, p_id) = global_frames.get(&n).unwrap(); + if let Some(p) = p_id { + current_period = match p { + 1 => "first_half", + 2 => "second_half", + 3 => "first_half_extratime", + 4 => "second_half_extratime", + 5 => "shootout", + _ => "other", + }; + } + if current_period != "other" { + sorted_n.push(n); + valid_periods.push(current_period.to_string()); + } + } + + if sorted_n.is_empty() { + return Err("No valid frames found in Sportec data after period filtering".to_string()); + } + + let n_to_row: AHashMap = + sorted_n.iter().enumerate().map(|(i, &n)| (n, i)).collect(); + let num_rows = sorted_n.len(); + + // 3. Data Ingestion Pass (Parallel) + let final_builder = fs_positions + .par_chunks(fs_positions.len().div_ceil(rayon::current_num_threads())) + .map(|pos_group| { + let mut builder = + TrackingDataBuilder::new_fixed(0, (num_rows as i64).saturating_sub(1)); + + // Register columns + builder.get_or_create_column_id( + "timestamp", + DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None), + ); + builder.get_or_create_column_id("period", DataType::Utf8); + let c_mid = builder.get_or_create_column_id("match/id", DataType::Utf8); + let c_v = builder.get_or_create_column_id("vendor/tracking/name", DataType::Utf8); + let c_bs = builder.get_or_create_column_id("ball_status", DataType::Boolean); + let c_bp = builder.get_or_create_column_id("ball_poss_team_id", DataType::Utf8); + + let c_hi = builder.get_or_create_column_id("teams/home/id", DataType::Utf8); + let c_ai = builder.get_or_create_column_id("teams/away/id", DataType::Utf8); + let c_hn = builder.get_or_create_column_id("teams/home/name", DataType::Utf8); + let c_an = builder.get_or_create_column_id("teams/away/name", DataType::Utf8); + let c_hc = builder.get_or_create_column_id("teams/home/jersey_colour", DataType::Utf8); + let c_ac = builder.get_or_create_column_id("teams/away/jersey_colour", DataType::Utf8); + + let mut entity_col_map = AHashMap::new(); + for (team, person) in &global_entities { + let team_role = team_map.get(team).map(|s| s.as_str()).unwrap_or(team); + let base = match team.as_str() { + "BALL" => "ball".to_string(), + "referee" | "referees" | "Referee" | "Referees" => { + format!("referees/{}", person) + } + _ => format!("teams/{}/players/{}", team_role, person), + }; + let ix = builder.get_or_create_column_id(&format!("{}/x", base), DataType::Float64); + let iy = builder.get_or_create_column_id(&format!("{}/y", base), DataType::Float64); + let iz = if team == "BALL" { + Some(builder.get_or_create_column_id(&format!("{}/z", base), DataType::Float64)) + } else { + None + }; + let iv = + builder.get_or_create_column_id(&format!("{}/vel", base), DataType::Float64); + let ia = + builder.get_or_create_column_id(&format!("{}/acc", base), DataType::Float64); + entity_col_map.insert((team.clone(), person.clone()), (ix, iy, iz, iv, ia)); + } + + let mut buf = Vec::new(); + for &fs_start in pos_group { + let mut cur_team = String::new(); + let mut cur_person = String::new(); + let mut cur_match = String::new(); + + let tag_end = raw_data[fs_start..].find(">").unwrap_or(0); + let mut reader = Reader::from_str(&raw_data[fs_start..fs_start + tag_end + 1]); + if let Ok(Event::Start(e)) | Ok(Event::Empty(e)) = reader.read_event_into(&mut buf) + { + for a in e.attributes().filter_map(|a| a.ok()) { + match a.key.as_ref() { + b"TeamId" => cur_team = String::from_utf8_lossy(&a.value).into_owned(), + b"PersonId" => { + cur_person = String::from_utf8_lossy(&a.value).into_owned() + } + b"MatchId" => { + cur_match = String::from_utf8_lossy(&a.value).into_owned() + } + _ => (), + } + } + } + buf.clear(); + + let chunk_end = fs_positions + .iter() + .find(|&&p| p > fs_start) + .cloned() + .unwrap_or(raw_data.len()); + let mut reader = Reader::from_str(&raw_data[fs_start..chunk_end]); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) + if e.name().as_ref() == b"Frame" => + { + let mut n = -1; + let mut x = 0.0; + let mut y = 0.0; + let mut z = 0.0; + let mut s = 0.0; + let mut a = 0.0; + let mut b_pos = 0; + let mut b_stat = 0; + let mut has_x = false; + let mut has_y = false; + let mut has_z = false; + let mut has_s = false; + let mut has_a = false; + let mut has_bp = false; + let mut has_bs = false; + + for attr in e.attributes().filter_map(|a| a.ok()) { + let val = std::str::from_utf8(&attr.value).unwrap_or(""); + match attr.key.as_ref() { + b"N" => n = val.parse().unwrap_or(-1), + b"X" => { + x = fast_float::parse(val).unwrap_or(0.0); + has_x = true; + } + b"Y" => { + y = fast_float::parse(val).unwrap_or(0.0); + has_y = true; + } + b"Z" => { + z = fast_float::parse(val).unwrap_or(0.0); + has_z = true; + } + b"S" => { + s = fast_float::parse(val).unwrap_or(0.0); + has_s = true; + } + b"A" => { + a = fast_float::parse(val).unwrap_or(0.0); + has_a = true; + } + b"BallPossession" => { + b_pos = val.parse().unwrap_or(0); + has_bp = true; + } + b"BallStatus" => { + b_stat = val.parse().unwrap_or(0); + has_bs = true; + } + _ => (), + } + } + + if let Some(&row) = n_to_row.get(&n) { + if has_bs { + builder.insert_bool_at_idx(row, c_bs, b_stat == 1); + } + if has_bp { + let tid = if b_pos == 1 { + home_id.as_ref() + } else if b_pos == 2 { + away_id.as_ref() + } else { + None + }; + if let Some(t) = tid { + builder.insert_string_at_idx(row, c_bp, t); + } + } + + if !cur_match.is_empty() { + builder.insert_string_at_idx(row, c_mid, &cur_match); + } + builder.insert_string_at_idx(row, c_v, "sportec"); + if let Some(id) = &home_id { + builder.insert_string_at_idx(row, c_hi, id); + } + if let Some(id) = &away_id { + builder.insert_string_at_idx(row, c_ai, id); + } + if let Some(nm) = &home_name { + builder.insert_string_at_idx(row, c_hn, nm); + } + if let Some(nm) = &away_name { + builder.insert_string_at_idx(row, c_an, nm); + } + if let Some(cl) = &home_color { + builder.insert_string_at_idx(row, c_hc, cl); + } + if let Some(cl) = &away_color { + builder.insert_string_at_idx(row, c_ac, cl); + } + + if let Some((ix, iy, iz, iv, ia)) = + entity_col_map.get(&(cur_team.clone(), cur_person.clone())) + { + if has_x { + builder.insert_float_at_idx(row, *ix, x); + } + if has_y { + builder.insert_float_at_idx(row, *iy, y); + } + if let Some(iz_idx) = iz + && has_z + { + builder.insert_float_at_idx(row, *iz_idx, z); + } + if has_s { + builder.insert_float_at_idx(row, *iv, s); + } + if has_a { + builder.insert_float_at_idx(row, *ia, a); + } + } + } + } + Ok(Event::Eof) => break, + _ => (), + } + buf.clear(); + } + } + Ok(builder) + }) + .reduce( + || { + Ok(TrackingDataBuilder::new_fixed( + 0, + (num_rows as i64).saturating_sub(1), + )) + }, + |a: Result, b: Result| match ( + a, b, + ) { + (Ok(mut a), Ok(b)) => { + a.merge(b); + Ok(a) + } + (Err(e), _) | (_, Err(e)) => Err(e), + }, + )?; + + // 4. Finalize Metadata (Interpolate T and M) + let mut final_builder = final_builder; + let c_ts = final_builder.get_or_create_column_id( + "timestamp", + DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None), + ); + let c_p = final_builder.get_or_create_column_id("period", DataType::Utf8); + let c_fid = final_builder.get_or_create_column_id("frame_id", DataType::Int64); + + for (i, &n) in sorted_n.iter().enumerate() { + let (ts_str, _) = global_frames.get(&n).cloned().unwrap_or((None, None)); + + final_builder.insert_string_at_idx(i, c_p, &valid_periods[i]); + final_builder.insert_int_at_idx(i, c_fid, n); + + if let Some(ts) = ts_str + && let Ok(dt) = chrono::DateTime::parse_from_rfc3339(&ts) + { + final_builder.insert_int_at_idx(i, c_ts, dt.timestamp_micros()); + } + } + + let mut container = final_builder.build().map_err(|e| e.to_string())?; + + if let (Some(x), Some(y)) = (pitch_x, pitch_y) { + let cs = CoordinateSystem::new( + Some((-x / 2.0, x / 2.0)), + Some((-y / 2.0, y / 2.0)), + Origin::Center, + Direction::Right, + Direction::Up, + Some(LengthUnit::Meters), + ); + container = container.with_coordinate_system(cs); + } + container = + container.with_schema_id(tdc_core::schema::SchemaId::new("cdf", "tracking", "0.2.3")); + container.validate().map_err(|e| e.to_string())?; + + Ok(container) +} diff --git a/crates/tdc-parsers/src/statsperform.rs b/crates/tdc-parsers/src/statsperform.rs new file mode 100644 index 0000000..4421de8 --- /dev/null +++ b/crates/tdc-parsers/src/statsperform.rs @@ -0,0 +1,169 @@ +use ahash::AHashMap; +use arrow_schema::DataType; +use rayon::prelude::*; +use std::fs; +use tdc_core::datacontainer::{TrackingDataBuilder, TrackingDataContainer}; +use tdc_core::schema::{CoordinateSystem, Direction, LengthUnit, Origin}; + +pub fn load_statsperform(path: &str) -> Result { + let raw_data = fs::read_to_string(path).map_err(|e| e.to_string())?; + + let lines: Vec<&str> = raw_data.lines().filter(|l| !l.is_empty()).collect(); + if lines.is_empty() { + return Err("Empty file".to_string()); + } + + let num_cpus = rayon::current_num_threads(); + let chunk_size = lines.len().div_ceil(num_cpus); + + let mut final_builder = lines + .par_chunks(chunk_size) + .map(|chunk| -> Result { + let mut builder = TrackingDataBuilder::new(); + let c_p = builder.get_or_create_column_id("period", DataType::Utf8); + let c_ts = builder.get_or_create_column_id( + "timestamp", + DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None), + ); + let c_s = builder.get_or_create_column_id("ball_status", DataType::Boolean); + let c_bx = builder.get_or_create_column_id("ball/x", DataType::Float64); + let c_by = builder.get_or_create_column_id("ball/y", DataType::Float64); + let c_bz = builder.get_or_create_column_id("ball/z", DataType::Float64); + let c_v = builder.get_or_create_column_id("vendor/tracking/name", DataType::Utf8); + + let mut player_map = AHashMap::new(); + + for line in chunk { + let mut parts = line.split(':'); + let frame_part = if let Some(p) = parts.next() { + p + } else { + continue; + }; + let (id_s, info_s) = if let Some(s) = frame_part.split_once(';') { + s + } else { + continue; + }; + + let f_id: i64 = id_s.parse().unwrap(); + let row = builder.get_row_index(f_id); + + // Frame info + let mut info = info_s.split(','); + if let (Some(ts_s), Some(p_s), Some(bs_s)) = (info.next(), info.next(), info.next()) + { + let ts_ms: i64 = ts_s.parse().unwrap(); + let p_id: i32 = p_s.parse().unwrap(); + let m_s: i32 = bs_s.parse().unwrap(); + let p_name = match p_id { + 1 => "first_half", + 2 => "second_half", + 3 => "first_half_extratime", + 4 => "second_half_extratime", + 5 => "shootout", + _ => "other", + }; + builder.insert_string_at_idx(row, c_p, p_name); + builder.insert_int_at_idx(row, c_ts, ts_ms * 1000); + builder.insert_bool_at_idx(row, c_s, m_s == 0); + builder.insert_string_at_idx(row, c_v, "statsperform"); + } + + // Players + if let Some(p_part) = parts.next() + && p_part != ";" + { + for p in p_part.split(';') { + if p.is_empty() { + continue; + } + let mut p_i = p.split(','); + let sid_s = p_i.next().unwrap(); + if sid_s == "2" { + continue; + } + let pid = p_i.next().unwrap(); + + let (ix, iy) = *player_map.entry((sid_s, pid)).or_insert_with(|| { + let sid: i32 = sid_s.parse().unwrap(); + let real = if sid > 2 { sid - 3 } else { sid }; + let team = if real == 0 { "home" } else { "away" }; + ( + builder.get_or_create_column_id( + &format!("teams/{}/players/{}/x", team, pid), + DataType::Float64, + ), + builder.get_or_create_column_id( + &format!("teams/{}/players/{}/y", team, pid), + DataType::Float64, + ), + ) + }); + + let _junk = p_i.next(); + if let Some(xs) = p_i.next() + && let Ok(x) = fast_float::parse(xs) + { + builder.insert_float_at_idx(row, ix, x); + } + if let Some(ys) = p_i.next() + && let Ok(y) = fast_float::parse(ys) + { + builder.insert_float_at_idx(row, iy, y); + } + } + } + + // Ball + if let Some(bp) = parts.next() { + let mut b_i = bp.split(';').next().unwrap().split(','); + if let (Some(xs), Some(ys)) = (b_i.next(), b_i.next()) { + if let Ok(x) = fast_float::parse(xs) { + builder.insert_float_at_idx(row, c_bx, x); + } + if let Ok(y) = fast_float::parse(ys) { + builder.insert_float_at_idx(row, c_by, y); + } + if let Some(zs) = b_i.next() + && let Ok(z) = fast_float::parse(zs) + { + builder.insert_float_at_idx(row, c_bz, z); + } + } + } + } + Ok(builder) + }) + .reduce( + || Ok(TrackingDataBuilder::new()), + |a, b| match (a, b) { + (Ok(mut a), Ok(b)) => { + a.merge(b); + Ok(a) + } + (Err(e), _) | (_, Err(e)) => Err(e), + }, + )?; + + final_builder.get_or_create_column_id("match/id", DataType::Utf8); + final_builder.get_or_create_column_id("teams/home/id", DataType::Utf8); + final_builder.get_or_create_column_id("teams/away/id", DataType::Utf8); + + let mut container = final_builder.build().map_err(|e| e.to_string())?; + + let cs = CoordinateSystem::new( + None, + None, + Origin::TopLeft, + Direction::Right, + Direction::Down, + Some(LengthUnit::Meters), + ); + container = container.with_coordinate_system(cs); + container = + container.with_schema_id(tdc_core::schema::SchemaId::new("cdf", "tracking", "0.2.3")); + container.validate().map_err(|e| e.to_string())?; + + Ok(container) +} diff --git a/crates/tdc-parsers/tests/test_hawkeye.rs b/crates/tdc-parsers/tests/test_hawkeye.rs new file mode 100644 index 0000000..9ae2c4b --- /dev/null +++ b/crates/tdc-parsers/tests/test_hawkeye.rs @@ -0,0 +1,257 @@ +use arrow_array::Array; +use tdc_parsers::load_hawkeye; + +#[test] +fn test_hawkeye_parser() { + let ball_content = r#"{ + "details": { + "teams": [ + {"id": {"heId": "team1"}, "name": "Team A", "home": true}, + {"id": {"heId": "team2"}, "name": "Team B", "home": false} + ] + }, + "samples": { + "ball": [ + {"pos": [1.0, 2.0, 3.0], "time": 0.0}, + {"pos": [4.0, 5.0, 6.0], "time": 0.04} + ] + } +}"#; + + let centroid_content = r#"{ + "details": { + "players": [ + {"id": {"heId": "p1"}, "teamId": {"heId": "team1"}, "jerseyNumber": "10"}, + {"id": {"heId": "p2"}, "teamId": {"heId": "team2"}, "jerseyNumber": "5"} + ] + }, + "samples": { + "people": [ + { + "personId": {"heId": "p1"}, + "role": {"id": 1, "name": "Outfielder"}, + "centroid": [{"pos": [10.0, 20.0], "time": 0.0}] + }, + { + "personId": {"heId": "p2"}, + "role": {"id": 1, "name": "Outfielder"}, + "centroid": [{"pos": [30.0, 40.0], "time": 0.0}] + }, + { + "personId": {"heId": "ref1"}, + "role": {"id": 4, "name": "Referee"}, + "centroid": [{"pos": [100.0, 200.0], "time": 0.0}] + }, + { + "personId": {"heId": "p1"}, + "role": {"id": 1, "name": "Outfielder"}, + "centroid": [{"pos": [40.0, 50.0], "time": 0.04}] + }, + { + "personId": {"heId": "p2"}, + "role": {"id": 1, "name": "Outfielder"}, + "centroid": [{"pos": [60.0, 70.0], "time": 0.04}] + } + ] + } +}"#; + + let joint_content = r#"{ + "samples": { + "people": [ + { + "personId": {"heId": "p1"}, + "joints": [ + { + "time": 0.0, + "lAnkle": [11.0, 21.0, 0.1], + "rAnkle": [12.0, 22.0, 0.2] + } + ] + }, + { + "personId": {"heId": "p1"}, + "joints": [ + { + "time": 0.04, + "lAnkle": [41.0, 51.0, 0.3], + "rAnkle": [42.0, 52.0, 0.4] + } + ] + } + ] + } +}"#; + + let meta_content = r#"{ + "Stadium": { + "PitchLength": 105.0, + "PitchWidth": 68.0 + } +}"#; + + // Use filenames that trigger period detection + let dir = tempfile::tempdir().unwrap(); + let ball_path = dir.path().join("match_1_1.football.samples.ball.json"); + let centroid_path = dir.path().join("match_1_1.football.samples.centroids.json"); + let joint_path = dir.path().join("match_1_1.football.samples.joints.json"); + let meta_path = dir.path().join("meta.json"); + + fs::write(&ball_path, ball_content).unwrap(); + fs::write(¢roid_path, centroid_content).unwrap(); + fs::write(&joint_path, joint_content).unwrap(); + fs::write(&meta_path, meta_content).unwrap(); + + let container = load_hawkeye( + &[ball_path.to_str().unwrap().to_string()], + &[centroid_path.to_str().unwrap().to_string()], + Some(&[joint_path.to_str().unwrap().to_string()]), + Some(meta_path.to_str().unwrap()), + ) + .unwrap(); + + // 1. Verify Schema and Field Mapping + let fields: Vec = container + .schema + .fields + .iter() + .map(|f| f.name().clone()) + .collect(); + + let expected_fields = vec![ + "ball/x", + "ball/y", + "ball/z", + "teams/home/players/10/x", + "teams/home/players/10/y", + "teams/home/players/10/landmarks/lAnkle/x", + "teams/home/players/10/landmarks/lAnkle/y", + "teams/home/players/10/landmarks/lAnkle/z", + "teams/home/players/10/landmarks/lAnkle/is_visible", + "teams/away/players/5/x", + "teams/away/players/5/y", + "referees/ref1/x", + "referees/ref1/y", + "period", + "vendor/tracking/name", + ]; + + for field in expected_fields { + assert!( + fields.contains(&field.to_string()), + "Missing field: {}", + field + ); + } + + // 2. Verify Data Types and Specific Values + let vendor_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "vendor/tracking/name") + .unwrap(); + let vendor_col = arrow_cast::cast( + container.data.column(vendor_idx), + &arrow_schema::DataType::Utf8, + ) + .unwrap(); + let vendor_val = vendor_col + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert_eq!(vendor_val, "hawkeye"); + + let period_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "period") + .unwrap(); + let period_col = arrow_cast::cast( + container.data.column(period_idx), + &arrow_schema::DataType::Utf8, + ) + .unwrap(); + let period_val = period_col + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert_eq!(period_val, "first_half"); + + assert_eq!(container.orientation, None); + + // 3. Verify Coordinate System + let cs = container + .coordinate_system + .expect("Coordinate system should be set"); + assert_eq!(cs.x_range, Some((-52.5, 52.5))); + assert_eq!(cs.y_range, Some((-34.0, 34.0))); + assert_eq!(cs.origin, tdc_core::schema::Origin::Center); + assert_eq!(cs.x_direction, tdc_core::schema::Direction::Right); + assert_eq!(cs.y_direction, tdc_core::schema::Direction::Up); + assert_eq!(cs.length_unit, Some(tdc_core::schema::LengthUnit::Meters)); + + // 4. Verify Entity Attributes + let check_float = |name: &str, row: usize, expected: f32| { + let idx = container + .schema + .fields + .iter() + .position(|f| f.name() == name) + .unwrap_or_else(|| panic!("Field {} not found", name)); + let col = container + .data + .column(idx) + .as_any() + .downcast_ref::() + .unwrap_or_else(|| panic!("Field {} is not Float32", name)); + assert!(!col.is_null(row), "Field {} is null at row {}", name, row); + assert!( + (col.value(row) - expected).abs() < 1e-6, + "Field {} value mismatch at row {}: expected {}, got {}", + name, + row, + expected, + col.value(row) + ); + }; + + check_float("ball/x", 0, 1.0); + check_float("ball/y", 0, 2.0); + check_float("ball/z", 0, 3.0); + check_float("teams/home/players/10/x", 0, 10.0); + check_float("teams/home/players/10/y", 0, 20.0); + check_float("teams/away/players/5/x", 0, 30.0); + check_float("teams/away/players/5/y", 0, 40.0); + check_float("referees/ref1/x", 0, 100.0); + check_float("referees/ref1/y", 0, 200.0); + + // Landmarks row 0 + check_float("teams/home/players/10/landmarks/lAnkle/x", 0, 11.0); + check_float("teams/home/players/10/landmarks/lAnkle/y", 0, 21.0); + check_float("teams/home/players/10/landmarks/lAnkle/z", 0, 0.1); + check_float("teams/home/players/10/landmarks/rAnkle/x", 0, 12.0); + check_float("teams/home/players/10/landmarks/rAnkle/y", 0, 22.0); + check_float("teams/home/players/10/landmarks/rAnkle/z", 0, 0.2); + + check_float("ball/x", 1, 4.0); + check_float("ball/y", 1, 5.0); + check_float("ball/z", 1, 6.0); + check_float("teams/home/players/10/x", 1, 40.0); + check_float("teams/home/players/10/y", 1, 50.0); + check_float("teams/away/players/5/x", 1, 60.0); + check_float("teams/away/players/5/y", 1, 70.0); + + // Landmarks row 1 + check_float("teams/home/players/10/landmarks/lAnkle/x", 1, 41.0); + check_float("teams/home/players/10/landmarks/lAnkle/y", 1, 51.0); + check_float("teams/home/players/10/landmarks/lAnkle/z", 1, 0.3); + check_float("teams/home/players/10/landmarks/rAnkle/x", 1, 42.0); + check_float("teams/home/players/10/landmarks/rAnkle/y", 1, 52.0); + check_float("teams/home/players/10/landmarks/rAnkle/z", 1, 0.4); +} + +use std::fs; diff --git a/crates/tdc-parsers/tests/test_sportec.rs b/crates/tdc-parsers/tests/test_sportec.rs new file mode 100644 index 0000000..b849840 --- /dev/null +++ b/crates/tdc-parsers/tests/test_sportec.rs @@ -0,0 +1,205 @@ +use arrow_array::Array; +use std::io::Write; +use tdc_parsers::load_sportec; +use tempfile::NamedTempFile; + +fn create_temp_file(content: &str) -> NamedTempFile { + let mut file = NamedTempFile::new().unwrap(); + write!(file, "{}", content).unwrap(); + file +} + +#[test] +fn test_sportec_parser() { + let xml_content = r#" + + + + + + + + + + + + + + + + +"#; + + let meta_content = r##" + + + + + + + + + +"##; + + let xml_file = create_temp_file(xml_content); + let meta_file = create_temp_file(meta_content); + + let container = load_sportec( + xml_file.path().to_str().unwrap(), + Some(meta_file.path().to_str().unwrap()), + ) + .unwrap(); + + // 1. Verify Schema and Field Mapping + let fields: Vec = container + .schema + .fields + .iter() + .map(|f| f.name().clone()) + .collect(); + + let expected_fields = vec![ + "ball/x", + "ball/y", + "ball/z", + "ball/vel", + "ball/acc", + "referees/DFL-OBJ-00011W/x", + "referees/DFL-OBJ-00011W/y", + "teams/home/players/player1/x", + "teams/home/players/player1/y", + "teams/home/players/player1/vel", + "teams/home/players/player1/acc", + "teams/home/id", + "teams/away/id", + "teams/home/name", + "teams/away/name", + "teams/home/jersey_colour", + "teams/away/jersey_colour", + "timestamp", + "period", + "match/id", + "vendor/tracking/name", + "ball_poss_team_id", + "ball_status", + ]; + + for field in expected_fields { + assert!( + fields.contains(&field.to_string()), + "Missing field: {}", + field + ); + } + + // 2. Verify Data Types and Specific Values + let vendor_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "vendor/tracking/name") + .unwrap(); + let vendor_col = arrow_cast::cast( + container.data.column(vendor_idx), + &arrow_schema::DataType::Utf8, + ) + .unwrap(); + let vendor_val = vendor_col + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert_eq!(vendor_val, "sportec"); + + let status_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "ball_status") + .unwrap(); + let status_val = container + .data + .column(status_idx) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert!(status_val); + + let ts_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "timestamp") + .unwrap(); + assert!(matches!( + container.data.column(ts_idx).data_type(), + arrow_schema::DataType::Timestamp(_, _) + )); + + let poss_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "ball_poss_team_id") + .unwrap(); + let poss_val = container + .data + .column(poss_idx) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert_eq!(poss_val, "TEAM_A"); + + assert_eq!(container.orientation, None); + + // 3. Verify Coordinate System + let cs = container + .coordinate_system + .expect("Coordinate system should be set"); + assert_eq!(cs.x_range, Some((-52.5, 52.5))); + assert_eq!(cs.y_range, Some((-34.0, 34.0))); + assert_eq!(cs.origin, tdc_core::schema::Origin::Center); + assert_eq!(cs.x_direction, tdc_core::schema::Direction::Right); + assert_eq!(cs.y_direction, tdc_core::schema::Direction::Up); + + // 4. Verify Entity Attributes + let check_float = |name: &str, expected: f64| { + let idx = container + .schema + .fields + .iter() + .position(|f| f.name() == name) + .unwrap_or_else(|| panic!("Field {} not found", name)); + let col = container + .data + .column(idx) + .as_any() + .downcast_ref::() + .unwrap_or_else(|| panic!("Field {} is not Float64", name)); + assert!(!col.is_null(0), "Field {} is null", name); + assert!( + (col.value(0) - expected).abs() < 1e-6, + "Field {} value mismatch: expected {}, got {}", + name, + expected, + col.value(0) + ); + }; + + check_float("ball/x", 10.0); + check_float("ball/y", 20.0); + check_float("ball/z", 1.5); + check_float("ball/vel", 5.0); + check_float("ball/acc", 1.0); + + check_float("referees/DFL-OBJ-00011W/x", 30.0); + check_float("referees/DFL-OBJ-00011W/y", 40.0); + + check_float("teams/home/players/player1/x", 50.0); + check_float("teams/home/players/player1/y", 60.0); + check_float("teams/home/players/player1/vel", 8.0); + check_float("teams/home/players/player1/acc", 2.0); +} diff --git a/crates/tdc-parsers/tests/test_statsperform.rs b/crates/tdc-parsers/tests/test_statsperform.rs new file mode 100644 index 0000000..66c13fe --- /dev/null +++ b/crates/tdc-parsers/tests/test_statsperform.rs @@ -0,0 +1,140 @@ +use arrow_array::Array; +use std::io::Write; +use tdc_parsers::load_statsperform; +use tempfile::NamedTempFile; + +fn create_temp_file(content: &str) -> NamedTempFile { + let mut file = NamedTempFile::new().unwrap(); + write!(file, "{}", content).unwrap(); + file +} + +#[test] +fn test_statsperform_parser() { + let content = "1000;12345,1,0:0,player1,10,10.0,20.0;1,player2,11,30.0,40.0:50.0,60.0,1.5"; + let file = create_temp_file(content); + + let container = load_statsperform(file.path().to_str().unwrap()).unwrap(); + + // 1. Verify Schema and Field Mapping + let fields: Vec = container + .schema + .fields + .iter() + .map(|f| f.name().clone()) + .collect(); + + let expected_fields = vec![ + "ball/x", + "ball/y", + "ball/z", + "teams/home/players/player1/x", + "teams/home/players/player1/y", + "teams/away/players/player2/x", + "teams/away/players/player2/y", + "timestamp", + "period", + "vendor/tracking/name", + "ball_status", + ]; + + for field in expected_fields { + assert!( + fields.contains(&field.to_string()), + "Missing field: {}", + field + ); + } + + // 2. Verify Data Types and Specific Values + let vendor_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "vendor/tracking/name") + .unwrap(); + let vendor_col = arrow_cast::cast( + container.data.column(vendor_idx), + &arrow_schema::DataType::Utf8, + ) + .unwrap(); + let vendor_val = vendor_col + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert_eq!(vendor_val, "statsperform"); + + let status_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "ball_status") + .unwrap(); + let status_val = container + .data + .column(status_idx) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + assert!(status_val); + + let ts_idx = container + .schema + .fields + .iter() + .position(|f| f.name() == "timestamp") + .unwrap(); + assert!(matches!( + container.data.column(ts_idx).data_type(), + arrow_schema::DataType::Timestamp(_, _) + )); + + assert_eq!(container.orientation, None); + + // 3. Verify Coordinate System + let cs = container + .coordinate_system + .expect("Coordinate system should be set"); + assert_eq!(cs.x_range, None); + assert_eq!(cs.y_range, None); + assert_eq!(cs.origin, tdc_core::schema::Origin::TopLeft); + assert_eq!(cs.x_direction, tdc_core::schema::Direction::Right); + assert_eq!(cs.y_direction, tdc_core::schema::Direction::Down); + assert_eq!(cs.length_unit, Some(tdc_core::schema::LengthUnit::Meters)); + + // 4. Verify Entity Attributes + let check_float = |name: &str, expected: f64| { + let idx = container + .schema + .fields + .iter() + .position(|f| f.name() == name) + .unwrap_or_else(|| panic!("Field {} not found", name)); + let col = container + .data + .column(idx) + .as_any() + .downcast_ref::() + .unwrap_or_else(|| panic!("Field {} is not Float64", name)); + assert!(!col.is_null(0), "Field {} is null", name); + assert!( + (col.value(0) - expected).abs() < 1e-6, + "Field {} value mismatch: expected {}, got {}", + name, + expected, + col.value(0) + ); + }; + + check_float("ball/x", 50.0); + check_float("ball/y", 60.0); + check_float("ball/z", 1.5); + + check_float("teams/home/players/player1/x", 10.0); + check_float("teams/home/players/player1/y", 20.0); + + check_float("teams/away/players/player2/x", 30.0); + check_float("teams/away/players/player2/y", 40.0); +} diff --git a/crates/tdc-python/Cargo.toml b/crates/tdc-python/Cargo.toml new file mode 100644 index 0000000..5b12c86 --- /dev/null +++ b/crates/tdc-python/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "tdc-python" +version = "0.1.0-alpha.1" +edition = "2024" +license = "MIT" +publish = false + +[lib] +name = "_tracking_data_container" +crate-type = ["cdylib"] + +[dependencies] +tdc-core = { path = "../tdc-core" } +tdc-parsers = { path = "../tdc-parsers", features = ["python"] } +tdc-ops = { path = "../tdc-ops" } +pyo3 = { version = "0.23", features = ["abi3-py310", "extension-module"] } +pyo3-arrow = { version = "0.7", default-features = false } +pyo3-log = { version = "0.12" } +datafusion = "45" +datafusion-functions = "45" +datafusion-functions-window = "45" +chrono = { version = "^0.4.42", default-features = false } +tokio = { version = "1", features = ["rt-multi-thread"] } +arrow-array = { version = "54" } +arrow-schema = { version = "54" } +arrow-cast = { version = "54" } +serde = { version = "1.0", features = ["derive"] } +strum = "0.27.2" +once_cell = "1.19" +num_cpus = "1.16" +futures = "0.3" +ahash = "0.8" diff --git a/crates/tdc-python/src/builder.rs b/crates/tdc-python/src/builder.rs new file mode 100644 index 0000000..996d6de --- /dev/null +++ b/crates/tdc-python/src/builder.rs @@ -0,0 +1,84 @@ +use crate::container::PyTrackingDataContainer; +use crate::error::IntoPyResult; +use crate::schema::PyDataField; +use pyo3::prelude::*; +use tdc_core::datacontainer::TrackingDataBuilder; +use tdc_core::error::{TrackingDataContainerError, TrackingDataContainerErrorKind}; + +#[pyclass(name = "_TrackingDataBuilder")] +pub struct PyTrackingDataBuilder { + pub inner: TrackingDataBuilder, +} + +#[pymethods] +impl PyTrackingDataBuilder { + #[new] + fn new() -> Self { + PyTrackingDataBuilder { + inner: TrackingDataBuilder::new(), + } + } + + fn insert_int(&mut self, frame_id: i64, col: Bound<'_, PyAny>, val: i64) -> PyResult<()> { + let col_name = self.extract_col_name(col)?; + self.inner.insert_int(frame_id, &col_name, val); + Ok(()) + } + + fn insert_float(&mut self, frame_id: i64, col: Bound<'_, PyAny>, val: f64) -> PyResult<()> { + let col_name = self.extract_col_name(col)?; + self.inner.insert_float(frame_id, &col_name, val); + Ok(()) + } + + fn insert_string(&mut self, frame_id: i64, col: Bound<'_, PyAny>, val: String) -> PyResult<()> { + let col_name = self.extract_col_name(col)?; + self.inner.insert_string(frame_id, &col_name, &val); + Ok(()) + } + + fn insert_bool(&mut self, frame_id: i64, col: Bound<'_, PyAny>, val: bool) -> PyResult<()> { + let col_name = self.extract_col_name(col)?; + self.inner.insert_bool(frame_id, &col_name, val); + Ok(()) + } + + fn merge(&mut self, other: &mut PyTrackingDataBuilder) { + // We take ownership of other's data by swapping it out + let other_inner = std::mem::replace(&mut other.inner, TrackingDataBuilder::new()); + self.inner.merge(other_inner); + } + + fn build(&mut self) -> PyResult { + // We take the current state to build + let builder = std::mem::replace(&mut self.inner, TrackingDataBuilder::new()); + let container = builder + .build() + .map_err(|e| { + TrackingDataContainerError::new(TrackingDataContainerErrorKind::InvalidParameters( + e.to_string(), + )) + }) + .into_pyresult()?; + Ok(PyTrackingDataContainer { inner: container }) + } +} + +impl PyTrackingDataBuilder { + fn extract_col_name(&self, col: Bound<'_, PyAny>) -> PyResult { + if let Ok(s) = col.extract::() { + Ok(s) + } else if let Ok(f) = col.extract::() { + Ok(f.inner.name()) + } else { + Err(pyo3::exceptions::PyTypeError::new_err( + "Column must be a string or a DataField object", + )) + } + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/crates/tdc-python/src/container.rs b/crates/tdc-python/src/container.rs new file mode 100644 index 0000000..53335b1 --- /dev/null +++ b/crates/tdc-python/src/container.rs @@ -0,0 +1,1013 @@ +use crate::coordinate_system::{PyCoordinateSystem, PyOrientationMode}; +use crate::error::IntoPyResult; +use crate::schema::{PyDataField, PyFields, PySchemaId}; +use crate::selector::{PyColumnSelector, PyExpr, PySelection}; +use tdc_core::Select; +use tdc_core::config::{CONTEXT, RUNTIME}; +use tdc_core::datacontainer::{LazyTrackingDataContainer, TrackingDataContainer}; +use tdc_core::schema::{DataField, Fields}; +use tdc_core::selector::expr::{Expr, expand_expr}; + +use datafusion::arrow::array::builder::{ + BooleanBuilder, Float32Builder, Float64Builder, Int64Builder, StringBuilder, + TimestampMicrosecondBuilder, UInt64Builder, +}; +use datafusion::arrow::array::{Array, ArrayRef, RecordBatch, StructArray}; +use datafusion::arrow::datatypes::{DataType, Field, Fields as ArrowFields, TimeUnit}; +use datafusion::prelude::*; +use futures::StreamExt; +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::prelude::*; +use pyo3::types::{PyCapsule, PyDict, PyList, PyType}; +use std::sync::Arc; + +fn block_on(future: F) -> F::Output { + RUNTIME.block_on(future) +} + +#[derive(FromPyObject)] +pub enum SelectItem { + Str(String), + Field(PyDataField), + Selection(PySelection), +} +#[derive(FromPyObject)] +pub enum SelectInput { + Single(SelectItem), + List(Vec), +} +#[derive(FromPyObject)] +#[allow(clippy::large_enum_variant)] +pub enum AddColumnValue { + Expr(PyExpr), + Array(PyObject), +} + +#[pyclass(name = "Frame")] +pub struct PyFrame { + pub values: Vec, + pub fields: Vec, + pub field_to_index: std::collections::HashMap, +} + +#[pymethods] +impl PyFrame { + fn __getitem__(&self, py: Python<'_>, key: Bound<'_, PyAny>) -> PyResult { + let name = if let Ok(s) = key.extract::() { + s + } else if let Ok(f) = key.getattr("_field") { + f.extract::()?.inner.name() + } else { + return Err(PyTypeError::new_err("Key must be string or DataField")); + }; + let idx = self + .field_to_index + .get(&name) + .ok_or_else(|| PyValueError::new_err(format!("Field '{}' not found", name)))?; + Ok(self.values[*idx].clone_ref(py)) + } + + fn to_dict(&self, py: Python<'_>) -> PyResult { + let dict = PyDict::new(py); + for (i, field) in self.fields.iter().enumerate() { + dict.set_item(field.name(), self.values[i].clone_ref(py))?; + } + Ok(dict.into_py(py)) + } + + fn __repr__(&self) -> String { + format!("", self.fields.len()) + } +} + +#[pyclass(name = "_TrackingDataContainer")] +pub struct PyTrackingDataContainer { + pub inner: TrackingDataContainer, +} + +#[pymethods] +impl PyTrackingDataContainer { + #[new] + #[pyo3(signature = (data, coordinate_system=None, orientation=None))] + pub fn new( + data: &Bound<'_, PyDict>, + coordinate_system: Option, + orientation: Option, + ) -> PyResult { + let mut fields = Vec::new(); + let mut arrays = Vec::new(); + let mut schema_fields = Vec::new(); + for (key, value) in data { + let col_name = key.extract::()?; + let data_field = DataField::from_name(&col_name); + let target_type = data_field.default_arrow_type(); + let array = build_arrow_array( + value + .downcast::() + .map_err(|_| PyTypeError::new_err("Must be list"))?, + &target_type, + )?; + fields.push(Field::new(&col_name, array.data_type().clone(), true)); + arrays.push(array); + schema_fields.push(data_field); + } + let struct_array = StructArray::try_new(ArrowFields::from(fields), arrays, None) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + let orientation = if let Some(o) = orientation { + use std::str::FromStr; + Some( + tdc_core::schema::OrientationMode::from_str(&o) + .map_err(|e| PyValueError::new_err(e.to_string()))?, + ) + } else { + None + }; + Ok(PyTrackingDataContainer { + inner: TrackingDataContainer { + data: struct_array, + schema: Fields { + fields: schema_fields, + }, + coordinate_system: coordinate_system.map(|cs| cs.inner), + orientation, + schema_id: None, + }, + }) + } + + #[staticmethod] + fn from_arrow( + _py: Python<'_>, + array_capsule: Bound<'_, PyCapsule>, + schema_capsule: Bound<'_, PyCapsule>, + coordinate_system: Option, + orientation: Option, + ) -> PyResult { + let array = pyo3_arrow::PyArray::from_arrow_pycapsule(&array_capsule, &schema_capsule)?; + let struct_array = array + .into_inner() + .0 + .as_any() + .downcast_ref::() + .ok_or_else(|| PyTypeError::new_err("Expected StructArray"))? + .clone(); + + let fields: Vec = struct_array + .fields() + .iter() + .map(|f| DataField::from_name(f.name())) + .collect(); + + let orientation = if let Some(o) = orientation { + use std::str::FromStr; + Some( + tdc_core::schema::OrientationMode::from_str(&o) + .map_err(|e| PyValueError::new_err(e.to_string()))?, + ) + } else { + None + }; + + Ok(PyTrackingDataContainer { + inner: TrackingDataContainer { + data: struct_array, + schema: Fields { fields }, + coordinate_system: coordinate_system.map(|cs| cs.inner), + orientation, + schema_id: None, + }, + }) + } + + fn lazy(&self) -> PyResult { + Ok(PyLazyTrackingDataContainer { + inner: self.inner.lazy().into_pyresult()?, + }) + } + fn select(&self, items: SelectInput) -> PyResult { + let mut fields = Vec::new(); + match items { + SelectInput::Single(item) => fields.extend(self.process_select_item(item)), + SelectInput::List(list) => { + for item in list { + fields.extend(self.process_select_item(item)); + } + } + } + Ok(PyTrackingDataContainer { + inner: self.inner.select(fields).into_pyresult()?, + }) + } + + #[pyo3(signature = (name, value, _schema_capsule=None))] + fn add_column( + &self, + py: Python<'_>, + name: String, + value: AddColumnValue, + _schema_capsule: Option>, + ) -> PyResult { + let schema_capsules = _schema_capsule + .map(|c| PyList::new(py, vec![c])) + .transpose()?; + self.add_columns(py, vec![name], vec![value], schema_capsules) + } + + #[pyo3(signature = (names, values, _schema_capsules=None))] + fn add_columns( + &self, + py: Python<'_>, + names: Vec, + values: Vec, + _schema_capsules: Option>, + ) -> PyResult { + let mut expr_names = Vec::new(); + let mut expr_values = Vec::new(); + let mut array_names = Vec::new(); + let mut array_values = Vec::new(); + for (i, (name, value)) in names.into_iter().zip(values).enumerate() { + match value { + AddColumnValue::Expr(e) => { + expr_names.push(name); + expr_values.push(e.inner); + } + AddColumnValue::Array(obj) => { + array_names.push(DataField::from_name(&name)); + let capsule = _schema_capsules + .as_ref() + .and_then(|l| l.get_item(i).ok()) + .ok_or_else(|| PyValueError::new_err("No schema capsule"))?; + array_values.push( + pyo3_arrow::PyArray::from_arrow_pycapsule( + obj.bind(py).downcast::()?, + capsule.downcast::()?, + )? + .into_inner() + .0, + ); + } + } + } + let mut current = self.inner.clone(); + if !expr_names.is_empty() { + current = current + .with_expr_columns_ctx(expr_names, expr_values, &CONTEXT, RUNTIME.handle()) + .into_pyresult()?; + } + if !array_names.is_empty() { + current = current.with_columns(array_names, array_values); + } + Ok(PyTrackingDataContainer { inner: current }.into_py(py)) + } + + fn add_column_per_player_rust( + &self, + _py: Python<'_>, + _name: String, + _value: AddColumnValue, + ) -> PyResult { + Err(PyValueError::new_err( + "Direct Rust function support has been removed. Use callable passing Arrow capsules instead.", + )) + } + + fn transform_coordinates(&self, cs: PyCoordinateSystem) -> PyResult { + Ok(PyTrackingDataContainer { + inner: self.inner.transform_coordinates(cs.inner).into_pyresult()?, + }) + } + fn transform_orientation(&self, mode: &str) -> PyResult { + use std::str::FromStr; + let mode = tdc_core::schema::OrientationMode::from_str(mode) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + Ok(PyTrackingDataContainer { + inner: self.inner.transform_orientation(mode).into_pyresult()?, + }) + } + fn with_orientation(&self, mode: &str) -> PyResult { + use std::str::FromStr; + let mode = tdc_core::schema::OrientationMode::from_str(mode) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + Ok(PyTrackingDataContainer { + inner: self.inner.clone().with_orientation(mode), + }) + } + fn with_coordinate_system(&self, cs: PyCoordinateSystem) -> Self { + PyTrackingDataContainer { + inner: self.inner.clone().with_coordinate_system(cs.inner), + } + } + + #[classmethod] + fn _from_capsule(_cls: &Bound<'_, PyType>, capsule: &Bound<'_, PyCapsule>) -> PyResult { + let container: &TrackingDataContainer = unsafe { capsule.reference() }; + Ok(PyTrackingDataContainer { + inner: container.clone(), + }) + } + + #[getter] + fn schema(&self) -> PyFields { + PyFields { + inner: self.inner.schema.clone(), + } + } + #[getter] + fn coordinate_system(&self) -> Option { + self.inner + .coordinate_system + .as_ref() + .map(|cs| PyCoordinateSystem { inner: cs.clone() }) + } + #[getter] + fn orientation_mode(&self) -> Option { + self.inner.orientation.map(|o| o.into()) + } + fn num_rows(&self) -> usize { + self.inner.num_rows() + } + fn slice(&self, offset: usize, length: usize) -> Self { + PyTrackingDataContainer { + inner: self.inner.slice(offset, length), + } + } + fn validate(&self) -> PyResult<()> { + self.inner.validate().into_pyresult() + } + + #[getter] + fn schema_id(&self) -> Option { + self.inner + .schema_id + .clone() + .map(|id| PySchemaId { inner: id }) + } + + fn with_schema_id(&self, schema_id: &PySchemaId) -> Self { + PyTrackingDataContainer { + inner: self.inner.clone().with_schema_id(schema_id.inner.clone()), + } + } + + fn validate_with_schema(&self, schema_id: &PySchemaId) -> PyResult<()> { + use crate::error::IntoPyResult; + self.inner + .schema + .validate_with_schema(&schema_id.inner) + .into_pyresult() + } + + fn save_parquet(&self, path: &str) -> PyResult<()> { + self.inner.save_parquet(path).into_pyresult() + } + + #[staticmethod] + fn load_parquet(path: &str) -> PyResult { + let inner = TrackingDataContainer::load_parquet(path).into_pyresult()?; + Ok(PyTrackingDataContainer { inner }) + } + + #[getter] + fn selector(&self) -> PyColumnSelector { + PyColumnSelector + } + + #[staticmethod] + #[pyo3(signature = (data, entity_to_team=None))] + fn pivot( + data: Bound<'_, PyAny>, + entity_to_team: Option>, + ) -> PyResult { + let capsules = data.call_method0("__arrow_c_array__")?; + let tuple = capsules.downcast::()?; + let array_item = tuple.get_item(0)?; + let array_capsule = array_item.downcast::()?; + let schema_item = tuple.get_item(1)?; + let schema_capsule = schema_item.downcast::()?; + + let array = pyo3_arrow::PyArray::from_arrow_pycapsule(array_capsule, schema_capsule)?; + let struct_array = array + .into_inner() + .0 + .as_any() + .downcast_ref::() + .ok_or_else(|| PyTypeError::new_err("Expected StructArray"))? + .clone(); + + let inner = TrackingDataContainer::pivot(struct_array, entity_to_team).into_pyresult()?; + Ok(PyTrackingDataContainer { inner }) + } + + fn materialize_frame<'py>(&self, py: Python<'py>, frame: usize) -> PyResult { + let mut values = Vec::new(); + let mut field_to_index = std::collections::HashMap::new(); + let mut fields = Vec::new(); + for (i, field) in self.inner.schema.fields.iter().enumerate() { + values.push(arrow_to_python(py, self.inner.data.column(i), frame)?); + field_to_index.insert(field.name(), i); + fields.push(field.clone()); + } + Ok(PyFrame { + values, + fields, + field_to_index, + }) + } + + fn to_arrow<'py>(&self, py: Python<'py>) -> PyResult> { + Ok( + pyo3_arrow::PyRecordBatch::new(RecordBatch::from(self.inner.data.clone())) + .to_pyarrow(py)? + .into_bound(py), + ) + } + fn __arrow_c_schema__<'py>(&self, py: Python<'py>) -> PyResult> { + Ok( + pyo3_arrow::PySchema::new(RecordBatch::from(self.inner.data.clone()).schema()) + .to_pyarrow(py)? + .into_bound(py), + ) + } + fn __arrow_c_array__<'py>( + &self, + py: Python<'py>, + _requested_schema: Option>, + ) -> PyResult> { + let rb = RecordBatch::from(self.inner.data.clone()); + let pyarrow_rb = pyo3_arrow::PyRecordBatch::new(rb).to_pyarrow(py)?; + Ok(pyarrow_rb + .call_method0(py, "__arrow_c_array__")? + .into_bound(py)) + } +} + +impl PyTrackingDataContainer { + fn process_select_item(&self, item: SelectItem) -> Vec { + match item { + SelectItem::Str(s) => { + if s.contains('*') { + let matched_names = expand_expr(&self.inner, &Expr::Wildcard(s)); + self.inner + .schema + .fields + .iter() + .filter(|f| matched_names.contains(&f.name())) + .cloned() + .collect() + } else { + vec![DataField::from_name(&s)] + } + } + SelectItem::Field(f) => vec![f.inner], + SelectItem::Selection(s) => expand_expr(&self.inner, &s.expr) + .into_iter() + .map(|n| DataField::from_name(&n)) + .collect(), + } + } +} + +#[pyclass(name = "_LazyTrackingDataContainer")] +pub struct PyLazyTrackingDataContainer { + pub inner: LazyTrackingDataContainer, +} + +#[pymethods] +impl PyLazyTrackingDataContainer { + fn collect(&self, py: Python<'_>) -> PyResult { + Ok(PyTrackingDataContainer { + inner: py + .allow_threads(|| block_on(async { self.inner.clone().collect().await })) + .into_pyresult()?, + }) + } + fn select(&self, items: SelectInput) -> PyResult { + let mut fields = Vec::new(); + match items { + SelectInput::Single(item) => fields.extend(self.process_select_item(item)), + SelectInput::List(list) => { + for item in list { + fields.extend(self.process_select_item(item)); + } + } + } + Ok(PyLazyTrackingDataContainer { + inner: self.inner.select(fields).into_pyresult()?, + }) + } + fn slice(&self, offset: usize, length: usize) -> PyResult { + Ok(PyLazyTrackingDataContainer { + inner: self.inner.slice(offset, length).into_pyresult()?, + }) + } + fn filter(&self, predicate: PyExpr) -> PyResult { + Ok(PyLazyTrackingDataContainer { + inner: self.inner.filter(predicate.inner).into_pyresult()?, + }) + } + + fn transform_coordinates(&self, cs: PyCoordinateSystem) -> PyResult { + Ok(PyLazyTrackingDataContainer { + inner: self.inner.transform_coordinates(cs.inner).into_pyresult()?, + }) + } + + fn transform_orientation(&self, mode: &str) -> PyResult { + use std::str::FromStr; + let mode = tdc_core::schema::OrientationMode::from_str(mode) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + Ok(PyLazyTrackingDataContainer { + inner: self.inner.transform_orientation(mode).into_pyresult()?, + }) + } + + #[pyo3(signature = (name, value, _schema_capsule=None))] + fn add_column( + &self, + py: Python<'_>, + name: String, + value: AddColumnValue, + _schema_capsule: Option>, + ) -> PyResult { + match value { + AddColumnValue::Expr(e) => Ok(PyLazyTrackingDataContainer { + inner: self.inner.add_column(&name, e.inner).into_pyresult()?, + }), + AddColumnValue::Array(obj) => { + let capsule = + _schema_capsule.ok_or_else(|| PyValueError::new_err("No schema capsule"))?; + let array = pyo3_arrow::PyArray::from_arrow_pycapsule( + obj.bind(py).downcast::()?, + capsule.downcast::()?, + )? + .into_inner() + .0; + Ok(PyLazyTrackingDataContainer { + inner: self.inner.add_array_column(&name, array).into_pyresult()?, + }) + } + } + } + + #[pyo3(signature = (names, values, _schema_capsules=None))] + fn add_columns( + &self, + _py: Python<'_>, + names: Vec, + values: Vec, + _schema_capsules: Option>, + ) -> PyResult { + let mut expr_names = Vec::new(); + let mut expr_values = Vec::new(); + + for (name, value) in names.into_iter().zip(values) { + match value { + AddColumnValue::Expr(e) => { + expr_names.push(name); + expr_values.push(e.inner); + } + AddColumnValue::Array(_) => { + return Err(PyValueError::new_err( + "Adding raw arrays to a lazy container is not yet supported.", + )); + } + } + } + + Ok(PyLazyTrackingDataContainer { + inner: self + .inner + .add_columns(expr_names, expr_values) + .into_pyresult()?, + }) + } + + fn add_column_udf(&self, name: String, func: PyObject, on: Vec) -> PyResult { + let py_udf = crate::udf::PyUDF::new(name.clone(), func); + let scalar_udf = datafusion::logical_expr::ScalarUDF::from(py_udf); + let args: Vec = + on.into_iter().map(datafusion::prelude::col).collect(); + let expr = scalar_udf.call(args); + + Ok(PyLazyTrackingDataContainer { + inner: self.inner.add_column(&name, expr).into_pyresult()?, + }) + } + + #[getter] + fn schema(&self) -> PyFields { + PyFields { + inner: self.inner.schema.clone(), + } + } + #[getter] + fn selector(&self) -> PyColumnSelector { + PyColumnSelector + } + #[getter] + fn orientation_mode(&self) -> Option { + self.inner.orientation.map(|o| o.into()) + } + fn __repr__(&self) -> String { + format!( + "", + self.inner.schema.len() + ) + } + fn __arrow_c_schema__<'py>(&self, py: Python<'py>) -> PyResult> { + Ok( + pyo3_arrow::PySchema::new(Arc::new(self.inner.df.schema().as_arrow().clone())) + .to_pyarrow(py)? + .into_bound(py), + ) + } + fn __arrow_c_stream__<'py>( + &self, + py: Python<'py>, + _requested_schema: Option>, + ) -> PyResult> { + let stream = py + .allow_threads(|| block_on(async { self.inner.df.clone().execute_stream().await })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + let schema = stream.schema(); + struct StreamReader { + stream: std::pin::Pin>, + schema: arrow_schema::SchemaRef, + } + impl Iterator for StreamReader { + type Item = Result; + fn next(&mut self) -> Option { + let next_batch = block_on(async { self.stream.next().await }); + next_batch.map(|res| { + res.map_err(|e| arrow_schema::ArrowError::ExternalError(Box::new(e))) + }) + } + } + impl datafusion::arrow::record_batch::RecordBatchReader for StreamReader { + fn schema(&self) -> arrow_schema::SchemaRef { + self.schema.clone() + } + } + let pyarrow_reader = + pyo3_arrow::PyRecordBatchReader::new(Box::new(StreamReader { stream, schema })) + .to_pyarrow(py)?; + Ok(pyarrow_reader + .call_method0(py, "__arrow_c_stream__")? + .into_bound(py)) + } + + fn melt(&self) -> PyResult { + Ok(PyLazyDataFrame { + df: self.inner.melt().into_pyresult()?, + }) + } +} + +#[pyclass(name = "LazyDataFrame", module = "tracking_data_container")] +pub struct PyLazyDataFrame { + df: datafusion::dataframe::DataFrame, +} + +#[pymethods] +impl PyLazyDataFrame { + fn __arrow_c_stream__<'py>( + &self, + py: Python<'py>, + _requested_schema: Option>, + ) -> PyResult> { + let stream = py + .allow_threads(|| block_on(async { self.df.clone().execute_stream().await })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + let schema = stream.schema(); + struct StreamReader { + stream: std::pin::Pin>, + schema: arrow_schema::SchemaRef, + } + impl Iterator for StreamReader { + type Item = Result; + fn next(&mut self) -> Option { + let next_batch = block_on(async { self.stream.next().await }); + next_batch.map(|res| { + res.map_err(|e| arrow_schema::ArrowError::ExternalError(Box::new(e))) + }) + } + } + impl datafusion::arrow::record_batch::RecordBatchReader for StreamReader { + fn schema(&self) -> arrow_schema::SchemaRef { + self.schema.clone() + } + } + let pyarrow_reader = + pyo3_arrow::PyRecordBatchReader::new(Box::new(StreamReader { stream, schema })) + .to_pyarrow(py)?; + Ok(pyarrow_reader + .call_method0(py, "__arrow_c_stream__")? + .into_bound(py)) + } + + fn extract_iceberg_metadata<'py>( + &self, + py: Python<'py>, + match_id: &str, + potential_constants: Vec, + ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { + use datafusion::prelude::*; + use pyo3::IntoPyObjectExt; + + // 1. Rename columns according to mapping + // COLUMN_MAPPING = {"match/id": "match_id", "competition/id": "competition_id", ...} + let df = self.df.clone(); + let mut select_exprs = Vec::new(); + for field in df.schema().fields() { + let name = field.name(); + if name == "match/id" { + select_exprs.push(col(name).alias("match_id")); + } else if name == "competition/id" { + select_exprs.push(col(name).alias("competition_id")); + } else if name == "season/id" { + select_exprs.push(col(name).alias("season_id")); + } else if name == "period_id" { + select_exprs.push(col(name).alias("period")); + } else if name == "player/id" { + select_exprs.push(col(name).alias("entity_id")); + } else { + select_exprs.push(col(name)); + } + } + + let df = py + .allow_threads(|| block_on(async { df.select(select_exprs) })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + + // 2. Add match_id column if not exists (we overwrite it for iceberg) + let df = py + .allow_threads(|| block_on(async { df.with_column("match_id", lit(match_id)) })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + + // 3. Aggregate constants + let group_exprs = vec![col("entity_id"), col("team/id"), col("entity_type")]; + let mut agg_exprs = Vec::new(); + for c in &potential_constants { + agg_exprs.push( + datafusion::functions_aggregate::expr_fn::count_distinct(col(c)) + .alias(format!("{}_count", c)), + ); + agg_exprs.push( + datafusion::functions_aggregate::expr_fn::first_value(col(c), None) + .alias(format!("{}_val", c)), + ); + } + + let agg_df = py + .allow_threads(|| block_on(async { df.clone().aggregate(group_exprs, agg_exprs) })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + + // 4. Collect agg_df to get the constants + let batches = py + .allow_threads(|| block_on(async { agg_df.collect().await })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + + // We will return the original (renamed, match_id added) PyLazyDataFrame, + // and the Arrow table of the aggregated stats. + // Then Python can figure out which columns are constant and drop them. + + // Convert batches to PyArrow Table + let schema = batches + .first() + .map(|b| b.schema()) + .unwrap_or_else(|| Arc::new(arrow_schema::Schema::empty())); + let mut arrow_batches = Vec::new(); + for b in batches { + arrow_batches.push(b); + } + + // Export aggregated table + let pyarrow = py.import_bound("pyarrow")?; + let table_class = pyarrow.getattr("Table")?; + let pa_table = pyo3_arrow::PyRecordBatchReader::new(Box::new( + datafusion::arrow::record_batch::RecordBatchIterator::new( + arrow_batches.into_iter().map(Ok), + schema, + ), + )) + .to_pyarrow(py)?; + let agg_table = table_class.call_method1("from_batches", (pa_table,))?; + + // Return a new PyLazyDataFrame for the main df + let new_df = PyLazyDataFrame { df }; + Ok((new_df.into_bound_py_any(py)?, agg_table)) + } + + fn drop_columns<'py>(&self, py: Python<'py>, cols: Vec) -> PyResult> { + let mut select_exprs = Vec::new(); + for field in self.df.schema().fields() { + let name = field.name(); + if !cols.contains(name) { + select_exprs.push(datafusion::prelude::col(name)); + } + } + let df = py + .allow_threads(|| block_on(async { self.df.clone().select(select_exprs) })) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + let new_df = PyLazyDataFrame { df }; + use pyo3::IntoPyObjectExt; + new_df.into_bound_py_any(py) + } +} + +impl PyLazyTrackingDataContainer { + fn process_select_item(&self, item: SelectItem) -> Vec { + match item { + SelectItem::Str(s) => { + if s.contains('*') { + let matched_names = expand_expr(&self.inner, &Expr::Wildcard(s)); + self.inner + .schema + .fields + .iter() + .filter(|f| matched_names.contains(&f.name())) + .cloned() + .collect() + } else { + vec![DataField::from_name(&s)] + } + } + SelectItem::Field(f) => vec![f.inner], + SelectItem::Selection(s) => expand_expr(&self.inner, &s.expr) + .into_iter() + .map(|n| DataField::from_name(&n)) + .collect(), + } + } +} + +#[pyfunction] +pub fn scan_parquet(_py: Python<'_>, path: String) -> PyResult { + let lazy_inner = block_on(async { + let df = CONTEXT + .read_parquet(&path, ParquetReadOptions::default()) + .await?; + let schema = Fields::new( + df.schema() + .as_arrow() + .fields() + .iter() + .map(|f| f.name().clone()) + .collect(), + ); + Ok::( + LazyTrackingDataContainer::new(df, schema, None, None, None), + ) + }) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + Ok(PyLazyTrackingDataContainer { inner: lazy_inner }) +} + +fn build_arrow_array(list: &Bound<'_, PyList>, dtype: &DataType) -> PyResult { + match dtype { + DataType::Float32 => { + let mut b = Float32Builder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.extract::()? as f32); + } + } + Ok(Arc::new(b.finish())) + } + DataType::Float64 => { + let mut b = Float64Builder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.extract::()?); + } + } + Ok(Arc::new(b.finish())) + } + DataType::Int64 => { + let mut b = Int64Builder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.extract::()?); + } + } + Ok(Arc::new(b.finish())) + } + DataType::UInt64 => { + let mut b = UInt64Builder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.extract::()?); + } + } + Ok(Arc::new(b.finish())) + } + DataType::Boolean => { + let mut b = BooleanBuilder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.extract::()?); + } + } + Ok(Arc::new(b.finish())) + } + DataType::Timestamp(TimeUnit::Microsecond, _) => { + let mut b = TimestampMicrosecondBuilder::with_capacity(list.len()); + for i in list { + if i.is_none() { + b.append_null(); + } else if let Ok(ts) = i.extract::() { + b.append_value(ts); + } else if let Ok(ts_val) = i.call_method0("timestamp") { + let ts = (ts_val.extract::()? * 1_000_000.0) as i64; + b.append_value(ts); + } else { + return Err(PyTypeError::new_err( + "Expected integer or datetime with timestamp method", + )); + } + } + Ok(Arc::new(b.finish())) + } + DataType::Utf8 | DataType::Dictionary(_, _) => { + let mut b = StringBuilder::with_capacity(list.len(), list.len() * 5); + for i in list { + if i.is_none() { + b.append_null(); + } else { + b.append_value(i.to_string()); + } + } + Ok(Arc::new(b.finish())) + } + _ => Err(PyTypeError::new_err(format!( + "Unsupported target type: {:?}", + dtype + ))), + } +} + +fn arrow_to_python(py: Python<'_>, array: &ArrayRef, row: usize) -> PyResult { + use datafusion::arrow::array::AsArray; + if array.is_null(row) { + return Ok(py.None()); + } + match array.data_type() { + DataType::Float32 => Ok(array + .as_primitive::() + .value(row) + .into_py(py)), + DataType::Float64 => Ok(array + .as_primitive::() + .value(row) + .into_py(py)), + DataType::Int64 => Ok(array + .as_primitive::() + .value(row) + .into_py(py)), + DataType::UInt64 => Ok(array + .as_primitive::() + .value(row) + .into_py(py)), + DataType::Boolean => Ok(array.as_boolean().value(row).into_py(py)), + DataType::Utf8 => Ok(array.as_string::().value(row).into_py(py)), + DataType::Timestamp(TimeUnit::Microsecond, _) => Ok(array + .as_primitive::() + .value(row) + .into_py(py)), + DataType::Dictionary(_, _) => { + let dict = + array + .as_any() + .downcast_ref::>() + .unwrap(); + let keys = dict.keys(); + let values = dict + .values() + .as_any() + .downcast_ref::() + .unwrap(); + Ok(values.value(keys.value(row) as usize).into_py(py)) + } + _ => Ok(py.None()), + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(scan_parquet, m)?)?; + Ok(()) +} diff --git a/crates/tdc-python/src/coordinate_system.rs b/crates/tdc-python/src/coordinate_system.rs new file mode 100644 index 0000000..bbcf1c5 --- /dev/null +++ b/crates/tdc-python/src/coordinate_system.rs @@ -0,0 +1,358 @@ +use pyo3::prelude::*; +use std::str::FromStr; +use tdc_core::schema::coordinate_system as core; + +#[pyclass(name = "Origin", eq, eq_int)] +#[derive(Clone, PartialEq)] +/// The location of the origin (0,0) in a coordinate system. +pub enum PyOrigin { + /// The center of the pitch/court. + Center, + /// The top-left corner of the pitch. + TopLeft, + /// The bottom-left corner of the pitch. + BottomLeft, +} + +#[pymethods] +impl PyOrigin { + #[classattr] + const CENTER: Self = Self::Center; + #[classattr] + const TOP_LEFT: Self = Self::TopLeft; + #[classattr] + const BOTTOM_LEFT: Self = Self::BottomLeft; + + fn __str__(&self) -> &str { + match self { + Self::Center => "center", + Self::TopLeft => "top_left", + Self::BottomLeft => "bottom_left", + } + } +} + +impl From for core::Origin { + fn from(o: PyOrigin) -> Self { + match o { + PyOrigin::Center => core::Origin::Center, + PyOrigin::TopLeft => core::Origin::TopLeft, + PyOrigin::BottomLeft => core::Origin::BottomLeft, + } + } +} + +#[pyclass(name = "Direction", eq, eq_int)] +#[derive(Clone, PartialEq)] +/// Possible axis directions. +pub enum PyDirection { + /// Values increase towards the right. + Right, + /// Values increase towards the left. + Left, + /// Values increase towards the top. + Up, + /// Values increase towards the bottom. + Down, +} + +#[pymethods] +impl PyDirection { + #[classattr] + const RIGHT: Self = Self::Right; + #[classattr] + const LEFT: Self = Self::Left; + #[classattr] + const UP: Self = Self::Up; + #[classattr] + const DOWN: Self = Self::Down; + + fn __str__(&self) -> &str { + match self { + Self::Right => "right", + Self::Left => "left", + Self::Up => "up", + Self::Down => "down", + } + } +} + +impl From for core::Direction { + fn from(d: PyDirection) -> Self { + match d { + PyDirection::Right => core::Direction::Right, + PyDirection::Left => core::Direction::Left, + PyDirection::Up => core::Direction::Up, + PyDirection::Down => core::Direction::Down, + } + } +} + +#[pyclass(name = "LengthUnit", eq, eq_int)] +#[derive(Clone, PartialEq)] +/// Standardized units of length. +pub enum PyLengthUnit { + /// Standard metric meter. + Meters, + /// Centimeter. + Centimeters, + /// Millimeter. + Millimeters, + /// Yard (approx 0.9144 meters). + Yards, + /// Inch. + Inches, + /// Foot. + Feet, + /// Normalized unit where coordinates represent a percentage of pitch dimensions. + Percent, +} + +#[pymethods] +impl PyLengthUnit { + #[classattr] + const METERS: Self = Self::Meters; + #[classattr] + const CENTIMETERS: Self = Self::Centimeters; + #[classattr] + const MILLIMETERS: Self = Self::Millimeters; + #[classattr] + const YARDS: Self = Self::Yards; + #[classattr] + const INCHES: Self = Self::Inches; + #[classattr] + const FEET: Self = Self::Feet; + #[classattr] + const PERCENT: Self = Self::Percent; + + fn __str__(&self) -> &str { + match self { + Self::Meters => "meters", + Self::Centimeters => "centimeters", + Self::Millimeters => "millimeters", + Self::Yards => "yards", + Self::Inches => "inches", + Self::Feet => "feet", + Self::Percent => "percent", + } + } +} + +impl From for tdc_core::schema::LengthUnit { + fn from(u: PyLengthUnit) -> Self { + match u { + PyLengthUnit::Meters => tdc_core::schema::LengthUnit::Meters, + PyLengthUnit::Centimeters => tdc_core::schema::LengthUnit::Centimeters, + PyLengthUnit::Millimeters => tdc_core::schema::LengthUnit::Millimeters, + PyLengthUnit::Yards => tdc_core::schema::LengthUnit::Yards, + PyLengthUnit::Inches => tdc_core::schema::LengthUnit::Inches, + PyLengthUnit::Feet => tdc_core::schema::LengthUnit::Feet, + PyLengthUnit::Percent => tdc_core::schema::LengthUnit::Percent, + } + } +} + +#[pyclass(name = "OrientationMode", eq, eq_int)] +#[derive(Clone, PartialEq)] +/// Logic for normalizing playing direction across periods or possession. +pub enum PyOrientationMode { + /// Coordinates are flipped so the team in possession always attacks from left to right. + BallOwningTeam, + /// The home team attacks left-to-right in the first half and right-to-left in the second. + HomeAway, + /// The away team attacks left-to-right in the first half and right-to-left in the second. + AwayHome, + /// The home team always attacks from left to right, regardless of period. + StaticHomeAway, + /// The away team always attacks from left to right, regardless of period. + StaticAwayHome, +} + +#[pymethods] +impl PyOrientationMode { + #[classattr] + const BALL_OWNING_TEAM: Self = Self::BallOwningTeam; + #[classattr] + const HOME_AWAY: Self = Self::HomeAway; + #[classattr] + const AWAY_HOME: Self = Self::AwayHome; + #[classattr] + const STATIC_HOME_AWAY: Self = Self::StaticHomeAway; + #[classattr] + const STATIC_AWAY_HOME: Self = Self::StaticAwayHome; + + fn __str__(&self) -> &str { + match self { + Self::BallOwningTeam => "ball_owning_team", + Self::HomeAway => "home_away", + Self::AwayHome => "away_home", + Self::StaticHomeAway => "static_home_away", + Self::StaticAwayHome => "static_away_home", + } + } +} + +impl From for tdc_core::schema::OrientationMode { + fn from(m: PyOrientationMode) -> Self { + match m { + PyOrientationMode::BallOwningTeam => tdc_core::schema::OrientationMode::BallOwningTeam, + PyOrientationMode::HomeAway => tdc_core::schema::OrientationMode::HomeAway, + PyOrientationMode::AwayHome => tdc_core::schema::OrientationMode::AwayHome, + PyOrientationMode::StaticHomeAway => tdc_core::schema::OrientationMode::StaticHomeAway, + PyOrientationMode::StaticAwayHome => tdc_core::schema::OrientationMode::StaticAwayHome, + } + } +} + +impl From for PyOrientationMode { + fn from(m: tdc_core::schema::OrientationMode) -> Self { + match m { + tdc_core::schema::OrientationMode::BallOwningTeam => PyOrientationMode::BallOwningTeam, + tdc_core::schema::OrientationMode::HomeAway => PyOrientationMode::HomeAway, + tdc_core::schema::OrientationMode::AwayHome => PyOrientationMode::AwayHome, + tdc_core::schema::OrientationMode::StaticHomeAway => PyOrientationMode::StaticHomeAway, + tdc_core::schema::OrientationMode::StaticAwayHome => PyOrientationMode::StaticAwayHome, + } + } +} + +#[pyclass(name = "_CoordinateSystem")] +#[derive(Clone)] +/// Defines a coordinate system for tracking data. +pub struct PyCoordinateSystem { + pub inner: core::CoordinateSystem, +} + +#[derive(FromPyObject)] +pub enum OriginInput { + Enum(PyOrigin), + Str(String), +} + +#[derive(FromPyObject)] +pub enum DirectionInput { + Enum(PyDirection), + Str(String), +} + +#[derive(FromPyObject)] +pub enum LengthUnitInput { + Enum(PyLengthUnit), + Str(String), +} + +#[pymethods] +impl PyCoordinateSystem { + #[new] + #[pyo3(signature = (x_range=None, y_range=None, origin=OriginInput::Str("center".to_string()), x_direction=DirectionInput::Str("right".to_string()), y_direction=DirectionInput::Str("up".to_string()), length_unit=None))] + fn new( + x_range: Option<(f64, f64)>, + y_range: Option<(f64, f64)>, + origin: OriginInput, + x_direction: DirectionInput, + y_direction: DirectionInput, + length_unit: Option, + ) -> PyResult { + let origin = match origin { + OriginInput::Enum(e) => e.into(), + OriginInput::Str(s) => core::Origin::from_str(&s).map_err(|e: strum::ParseError| { + pyo3::exceptions::PyValueError::new_err(e.to_string()) + })?, + }; + let x_direction = match x_direction { + DirectionInput::Enum(e) => e.into(), + DirectionInput::Str(s) => { + core::Direction::from_str(&s).map_err(|e: strum::ParseError| { + pyo3::exceptions::PyValueError::new_err(e.to_string()) + })? + } + }; + let y_direction = match y_direction { + DirectionInput::Enum(e) => e.into(), + DirectionInput::Str(s) => { + core::Direction::from_str(&s).map_err(|e: strum::ParseError| { + pyo3::exceptions::PyValueError::new_err(e.to_string()) + })? + } + }; + let length_unit = if let Some(u) = length_unit { + let core_unit = match u { + LengthUnitInput::Enum(e) => e.into(), + LengthUnitInput::Str(s) => { + tdc_core::schema::LengthUnit::from_str(&s).map_err(|e: strum::ParseError| { + pyo3::exceptions::PyValueError::new_err(e.to_string()) + })? + } + }; + Some(core_unit) + } else { + None + }; + + Ok(PyCoordinateSystem { + inner: core::CoordinateSystem::new( + x_range, + y_range, + origin, + x_direction, + y_direction, + length_unit, + ), + }) + } + + fn __repr__(&self) -> String { + format!( + "", + self.inner.origin, + self.inner.x_direction, + self.inner.y_direction, + self.inner.length_unit + ) + } + + #[getter] + fn origin(&self) -> String { + self.inner.origin.to_string() + } + + #[getter] + fn x_range(&self) -> Option<(f64, f64)> { + self.inner.x_range + } + + #[getter] + fn y_range(&self) -> Option<(f64, f64)> { + self.inner.y_range + } + + #[getter] + fn x_direction(&self) -> String { + self.inner.x_direction.to_string() + } + + #[getter] + fn y_direction(&self) -> String { + self.inner.y_direction.to_string() + } + + #[getter] + fn length_unit(&self) -> Option { + self.inner + .length_unit + .map(|u: tdc_core::schema::LengthUnit| u.to_string()) + } + + fn __eq__(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/crates/tdc-python/src/error.rs b/crates/tdc-python/src/error.rs new file mode 100644 index 0000000..c3be3d5 --- /dev/null +++ b/crates/tdc-python/src/error.rs @@ -0,0 +1,92 @@ +use pyo3::prelude::*; +use pyo3::{PyErr, PyResult, create_exception, exceptions::PyException}; +use tdc_core::error::{ + TrackingDataContainerError as CoreError, TrackingDataContainerErrorKind, + TrackingDataContainerResult, +}; + +create_exception!( + _tracking_data_container, + TrackingDataContainerError, + PyException, + "The base class for all errors" +); + +create_exception!( + _tracking_data_container, + InvalidParametersError, + TrackingDataContainerError, + "Provided parameters are invalid" +); + +create_exception!( + _tracking_data_container, + SchemaError, + TrackingDataContainerError, + "Schema validation failed" +); + +create_exception!( + _tracking_data_container, + SchemaWarning, + pyo3::exceptions::PyWarning, + "Schema validation warning" +); + +pub fn core_to_py_err(err: CoreError) -> PyErr { + let message = err.to_string(); + match err.kind { + TrackingDataContainerErrorKind::InvalidParameters(_) => { + InvalidParametersError::new_err(message) + } + TrackingDataContainerErrorKind::SchemaError(_) => SchemaError::new_err(message), + TrackingDataContainerErrorKind::SchemaWarning(_) => SchemaWarning::new_err(message), + TrackingDataContainerErrorKind::ArrowError(_) => { + TrackingDataContainerError::new_err(message) + } + TrackingDataContainerErrorKind::DataFusionError(_) => { + TrackingDataContainerError::new_err(message) + } + TrackingDataContainerErrorKind::ParquetError(_) => { + pyo3::exceptions::PyIOError::new_err(message) + } + TrackingDataContainerErrorKind::IOError(_) => pyo3::exceptions::PyIOError::new_err(message), + TrackingDataContainerErrorKind::Internal(_) => { + pyo3::exceptions::PyRuntimeError::new_err(message) + } + } +} + +pub trait IntoPyResult { + type Inner; + fn into_pyresult(self) -> PyResult; +} + +impl IntoPyResult for TrackingDataContainerResult { + type Inner = T; + fn into_pyresult(self) -> PyResult { + self.map_err(core_to_py_err) + } +} + +impl IntoPyResult for Result { + type Inner = T; + fn into_pyresult(self) -> PyResult { + self.map_err(pyo3::exceptions::PyValueError::new_err) + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + let py = m.py(); + m.add( + "TrackingDataContainerError", + py.get_type::(), + )?; + m.add( + "InvalidParametersError", + py.get_type::(), + )?; + m.add("SchemaError", py.get_type::())?; + m.add("SchemaWarning", py.get_type::())?; + Ok(()) +} diff --git a/crates/tdc-python/src/io.rs b/crates/tdc-python/src/io.rs new file mode 100644 index 0000000..985d51a --- /dev/null +++ b/crates/tdc-python/src/io.rs @@ -0,0 +1,7 @@ +use pyo3::prelude::*; +use tdc_parsers; + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + tdc_parsers::python::register(m)?; + Ok(()) +} diff --git a/crates/tdc-python/src/lib.rs b/crates/tdc-python/src/lib.rs new file mode 100644 index 0000000..ecc4158 --- /dev/null +++ b/crates/tdc-python/src/lib.rs @@ -0,0 +1,35 @@ +#![allow(deprecated)] +use pyo3::prelude::*; + +pub mod builder; +pub mod container; +pub mod coordinate_system; +pub mod error; +pub mod io; +pub mod ops; +pub mod schema; +pub mod selector; +pub mod udf; + +#[pymodule(gil_used = false)] +fn _tracking_data_container(m: &Bound<'_, PyModule>) -> PyResult<()> { + pyo3_log::init(); + + builder::register(m)?; + container::register(m)?; + coordinate_system::register(m)?; + error::register(m)?; + schema::register(m)?; + selector::register(m)?; + udf::register(m)?; + io::register(m)?; + ops::register(m)?; + + m.add("__version__", env!("CARGO_PKG_VERSION"))?; + m.add( + "CORE_COLUMNS", + tdc_core::schema::field::CORE_COLUMNS.to_vec(), + )?; + + Ok(()) +} diff --git a/crates/tdc-python/src/mod.rs b/crates/tdc-python/src/mod.rs new file mode 100644 index 0000000..e8ba3d4 --- /dev/null +++ b/crates/tdc-python/src/mod.rs @@ -0,0 +1,20 @@ +pub mod builder; +pub mod container; +pub mod coordinate_system; +pub mod error; +pub mod schema; +pub mod selector; +pub mod udf; + +use pyo3::prelude::*; + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + builder::register(m)?; + container::register(m)?; + coordinate_system::register(m)?; + error::register(m)?; + schema::register(m)?; + selector::register(m)?; + udf::register(m)?; + Ok(()) +} diff --git a/crates/tdc-python/src/ops.rs b/crates/tdc-python/src/ops.rs new file mode 100644 index 0000000..90df574 --- /dev/null +++ b/crates/tdc-python/src/ops.rs @@ -0,0 +1,19 @@ +use pyo3::prelude::*; +use tdc_ops; + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + // We can either register it under a sub-module or directly. + // The user example suggested `import my_rust_lib`, so we'll + // just add the functions to the main module or a sub-module. + + // Let's add it to a sub-module 'ops' + let ops = PyModule::new_bound(m.py(), "ops")?; + ops.add_function(wrap_pyfunction!(tdc_ops::compute_speed, &ops)?)?; + ops.add_function(wrap_pyfunction!(tdc_ops::compute_acceleration, &ops)?)?; + ops.add_function(wrap_pyfunction!(tdc_ops::compute_jerk, &ops)?)?; + ops.add_function(wrap_pyfunction!(tdc_ops::compute_angular_velocity, &ops)?)?; + ops.add_function(wrap_pyfunction!(tdc_ops::butterworth_filter, &ops)?)?; + m.add_submodule(&ops)?; + + Ok(()) +} diff --git a/crates/tdc-python/src/schema.rs b/crates/tdc-python/src/schema.rs new file mode 100644 index 0000000..58d5bcb --- /dev/null +++ b/crates/tdc-python/src/schema.rs @@ -0,0 +1,297 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; +use tdc_core::schema::{DataField, Fields, TrackedEntity}; + +#[pyclass(name = "Attr", eq, eq_int)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum PyAttr { + X, + Y, + Z, + Speed, + Period, + Timestamp, + FrameId, +} + +#[pyclass(name = "_DataField")] +#[derive(Clone)] +pub struct PyDataField { + pub inner: DataField, +} + +#[pymethods] +impl PyDataField { + #[classmethod] + fn from_name(_cls: &Bound<'_, PyType>, name: &str) -> Self { + PyDataField { + inner: DataField::from_name(name), + } + } + fn is_frame(&self) -> bool { + matches!(self.inner, DataField::Frame { .. }) + } + fn name(&self) -> String { + self.inner.name() + } + fn attribute(&self) -> String { + use tdc_core::schema::attribute::Attribute; + match &self.inner { + DataField::Frame { attribute } => attribute.name().to_string(), + DataField::TrackedEntity { attribute, .. } => attribute.name().to_string(), + } + } + + fn to_iceberg_name(&self) -> String { + match self.inner.name().as_str() { + "match/id" => "match_id".to_string(), + "competition/id" => "competition_id".to_string(), + "season/id" => "season_id".to_string(), + "player/id" => "entity_id".to_string(), + "team/id" => "team_id".to_string(), + other => other.to_string(), + } + } + fn entity(&self) -> Option { + match &self.inner { + DataField::Frame { .. } => None, + DataField::TrackedEntity { entity, .. } => Some(PyTrackedEntity { + inner: entity.clone(), + }), + } + } + #[getter] + fn team_id(&self) -> Option { + match &self.inner { + DataField::TrackedEntity { + entity: TrackedEntity::Player { team_id, .. }, + .. + } => Some(team_id.clone()), + _ => None, + } + } + #[getter] + fn player_id(&self) -> Option { + match &self.inner { + DataField::TrackedEntity { + entity: + TrackedEntity::Player { player_id, .. } + | TrackedEntity::Referee { + referee_id: player_id, + }, + .. + } => Some(player_id.clone()), + _ => None, + } + } + fn __repr__(&self) -> String { + self.inner.name() + } +} + +#[pyclass(name = "_TrackedEntity")] +#[derive(Clone)] +pub struct PyTrackedEntity { + pub inner: TrackedEntity, +} + +#[pymethods] +impl PyTrackedEntity { + fn is_ball(&self) -> bool { + matches!(self.inner, TrackedEntity::Ball) + } + fn is_player(&self) -> bool { + matches!(self.inner, TrackedEntity::Player { .. }) + } + fn is_team(&self) -> bool { + matches!(self.inner, TrackedEntity::Team { .. }) + } + fn is_referee(&self) -> bool { + matches!(self.inner, TrackedEntity::Referee { .. }) + } + fn team_id(&self) -> Option { + match &self.inner { + TrackedEntity::Player { team_id, .. } | TrackedEntity::Team { team_id } => { + Some(team_id.clone()) + } + _ => None, + } + } + fn player_id(&self) -> Option { + match &self.inner { + TrackedEntity::Player { player_id, .. } + | TrackedEntity::Referee { + referee_id: player_id, + } => Some(player_id.clone()), + _ => None, + } + } + fn __repr__(&self) -> String { + format!("{:?}", self.inner) + } +} + +#[pyclass(name = "_Fields")] +pub struct PyFields { + pub inner: Fields, +} + +#[pymethods] +impl PyFields { + #[new] + fn new(names: Vec) -> Self { + PyFields { + inner: Fields::new(names), + } + } + fn __len__(&self) -> usize { + self.inner.len() + } + fn __getitem__(&self, index: usize) -> PyResult { + Ok(PyDataField { + inner: self + .inner + .fields + .get(index) + .ok_or_else(|| pyo3::exceptions::PyIndexError::new_err("Index out of range"))? + .clone(), + }) + } + fn player_entities(&self, team_id: Option) -> Vec { + self.inner + .player_entities(team_id.as_deref()) + .into_iter() + .map(|p| PyTrackedEntity { inner: p }) + .collect() + } + fn team_entities(&self) -> Vec { + self.inner + .team_entities() + .into_iter() + .map(|t| PyTrackedEntity { inner: t }) + .collect() + } + fn referee_entities(&self) -> Vec { + self.inner + .referee_entities() + .into_iter() + .map(|r| PyTrackedEntity { inner: r }) + .collect() + } + fn __iter__(slf: PyRef<'_, Self>) -> FieldIterator { + FieldIterator { + fields: slf.inner.fields.clone(), + index: 0, + } + } + fn validate_with_schema(&self, schema_id: &PySchemaId) -> PyResult<()> { + use crate::error::IntoPyResult; + self.inner + .validate_with_schema(&schema_id.inner) + .into_pyresult() + } +} + +#[pyclass] +pub struct FieldIterator { + fields: Vec, + index: usize, +} + +#[pymethods] +impl FieldIterator { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + if slf.index < slf.fields.len() { + let res = Some(PyDataField { + inner: slf.fields[slf.index].clone(), + }); + slf.index += 1; + res + } else { + None + } + } +} + +#[pyclass(name = "_SchemaId")] +#[derive(Clone)] +pub struct PySchemaId { + pub inner: tdc_core::schema::SchemaId, +} + +#[pymethods] +impl PySchemaId { + #[new] + fn new(sport: String, data_kind: String, version: String) -> Self { + Self { + inner: tdc_core::schema::SchemaId::new(sport, data_kind, version), + } + } + + #[staticmethod] + fn from_string(s: &str) -> PyResult { + tdc_core::schema::SchemaId::parse(s) + .map(|inner| Self { inner }) + .ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err(format!( + "Invalid schema ID: '{}'. Expected format: 'sport/data_kind/version'", + s + )) + }) + } + + #[getter] + fn sport(&self) -> &str { + &self.inner.sport + } + + #[getter] + fn data_kind(&self) -> &str { + &self.inner.data_kind + } + + #[getter] + fn version(&self) -> &str { + &self.inner.version + } + + fn __str__(&self) -> String { + self.inner.to_string() + } + + fn __repr__(&self) -> String { + format!("SchemaId({})", self.inner) + } + + fn __eq__(&self, other: &Self) -> bool { + self.inner == other.inner + } + + fn __hash__(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.inner.hash(&mut hasher); + hasher.finish() + } +} + +#[pyfunction] +pub fn list_schemas() -> Vec { + tdc_core::schema::list_schemas() + .iter() + .map(|s| s.to_string()) + .collect() +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(list_schemas, m)?)?; + Ok(()) +} diff --git a/crates/tdc-python/src/selector.rs b/crates/tdc-python/src/selector.rs new file mode 100644 index 0000000..8912362 --- /dev/null +++ b/crates/tdc-python/src/selector.rs @@ -0,0 +1,361 @@ +use datafusion::common::tree_node::TreeNode; +use datafusion::prelude as df; +use datafusion_functions::expr_fn::coalesce; +use datafusion_functions::math::expr_fn::{power, sqrt}; +use datafusion_functions_window::expr_fn::lag; +use pyo3::exceptions::PyTypeError; +use pyo3::prelude::*; +use tdc_core::selector::expr::Expr as CoreExpr; + +#[pyclass(name = "Selection")] +#[derive(Clone)] +pub struct PySelection { + pub expr: CoreExpr, +} + +#[pymethods] +impl PySelection { + fn __call__(&self) -> Self { + self.clone() + } + fn attributes(&self, names: Vec) -> Self { + PySelection { + expr: CoreExpr::Attributes { + inner: Box::new(self.expr.clone()), + names, + }, + } + } + #[getter] + fn expr(&self) -> String { + format!("{:?}", self.expr) + } +} + +#[pyclass(name = "ColumnSelector")] +pub struct PyColumnSelector; + +#[pymethods] +impl PyColumnSelector { + #[new] + fn new() -> Self { + PyColumnSelector + } + + fn ball(&self) -> PySelection { + PySelection { + expr: CoreExpr::Ball, + } + } + fn players(&self, team_id: Option) -> PySelection { + PySelection { + expr: CoreExpr::Players { team_id }, + } + } + fn player(&self, player_id: String, team_id: Option) -> PySelection { + PySelection { + expr: CoreExpr::Player { team_id, player_id }, + } + } + fn team(&self, team_id: &str) -> PySelection { + PySelection { + expr: CoreExpr::Players { + team_id: Some(team_id.to_string()), + }, + } + } +} + +#[pyclass(name = "Expr")] +#[derive(Clone)] +pub struct PyExpr { + pub inner: df::Expr, +} + +#[pymethods] +impl PyExpr { + fn __repr__(&self) -> String { + format!("{:?}", self.inner) + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __add__(&self, other: PyExpr) -> PyExpr { + PyExpr { + inner: self.inner.clone() + other.inner, + } + } + fn __sub__(&self, other: PyExpr) -> PyExpr { + PyExpr { + inner: self.inner.clone() - other.inner, + } + } + + fn __mul__(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone() * e.inner, + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone() * df::lit(f), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone() * df::lit(i), + }) + } else { + Err(PyTypeError::new_err("Invalid type for multiplication")) + } + } + + fn mul(&self, other: Bound<'_, PyAny>) -> PyResult { + self.__mul__(other) + } + + fn __truediv__(&self, other: PyExpr) -> PyExpr { + PyExpr { + inner: self.inner.clone() / other.inner, + } + } + + fn __and__(&self, other: PyExpr) -> PyExpr { + PyExpr { + inner: self.inner.clone().and(other.inner), + } + } + + fn __or__(&self, other: PyExpr) -> PyExpr { + PyExpr { + inner: self.inner.clone().or(other.inner), + } + } + + fn __pow__(&self, exponent: f64, _modulo: Option>) -> PyExpr { + PyExpr { + inner: power(self.inner.clone(), df::lit(exponent)), + } + } + + fn __gt__(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt(df::lit(i)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn gt(&self, other: Bound<'_, PyAny>) -> PyResult { + self.__gt__(other) + } + + fn __lt__(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt(df::lit(i)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn lt(&self, other: Bound<'_, PyAny>) -> PyResult { + self.__lt__(other) + } + + fn __eq__(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().eq(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().eq(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().eq(df::lit(i)), + }) + } else if let Ok(s) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().eq(df::lit(s)), + }) + } else if let Ok(b) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().eq(df::lit(b)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn eq(&self, other: Bound<'_, PyAny>) -> PyResult { + self.__eq__(other) + } + + fn ne(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().not_eq(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().not_eq(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().not_eq(df::lit(i)), + }) + } else if let Ok(s) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().not_eq(df::lit(s)), + }) + } else if let Ok(b) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().not_eq(df::lit(b)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn ge(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt_eq(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt_eq(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().gt_eq(df::lit(i)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn le(&self, other: Bound<'_, PyAny>) -> PyResult { + if let Ok(e) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt_eq(e.inner), + }) + } else if let Ok(f) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt_eq(df::lit(f)), + }) + } else if let Ok(i) = other.extract::() { + Ok(PyExpr { + inner: self.inner.clone().lt_eq(df::lit(i)), + }) + } else { + Err(PyTypeError::new_err("Invalid type for comparison")) + } + } + + fn diff(&self) -> Self { + PyExpr { + inner: self.inner.clone() - lag(self.inner.clone(), Some(1), None), + } + } + + fn fill_null(&self, value: Bound<'_, PyAny>) -> PyResult { + let replacement = if let Ok(f) = value.extract::() { + df::lit(f) + } else if let Ok(i) = value.extract::() { + df::lit(i) + } else if let Ok(s) = value.extract::() { + df::lit(s) + } else if let Ok(b) = value.extract::() { + df::lit(b) + } else if let Ok(e) = value.extract::() { + e.inner + } else { + return Err(PyTypeError::new_err("Unsupported type for fill_null")); + }; + + Ok(PyExpr { + inner: coalesce(vec![self.inner.clone(), replacement]), + }) + } + + fn pow(&self, exponent: f64) -> Self { + self.__pow__(exponent, None) + } + fn sqrt(&self) -> Self { + PyExpr { + inner: sqrt(self.inner.clone()), + } + } + + fn replace_columns(&self, mapping: std::collections::HashMap) -> PyExpr { + let transformed = self + .inner + .clone() + .transform(|expr| { + if let df::Expr::Column(ref col) = expr + && let Some(new_name) = mapping.get(&col.name) + { + return Ok(datafusion::common::tree_node::Transformed::yes( + df::Expr::Column(datafusion::common::Column::from_name(new_name)), + )); + } + Ok(datafusion::common::tree_node::Transformed::no(expr)) + }) + .unwrap() + .data; + PyExpr { inner: transformed } + } +} + +#[pyfunction] +pub fn col(name: &str) -> PyExpr { + PyExpr { + inner: df::col(name), + } +} +#[pyfunction] +pub fn lit(value: Bound<'_, PyAny>) -> PyResult { + if let Ok(f) = value.extract::() { + Ok(PyExpr { inner: df::lit(f) }) + } else if let Ok(i) = value.extract::() { + Ok(PyExpr { inner: df::lit(i) }) + } else if let Ok(s) = value.extract::() { + Ok(PyExpr { inner: df::lit(s) }) + } else if let Ok(b) = value.extract::() { + Ok(PyExpr { inner: df::lit(b) }) + } else { + Err(PyTypeError::new_err("Unsupported literal type")) + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(col, m)?)?; + m.add_function(wrap_pyfunction!(lit, m)?)?; + Ok(()) +} diff --git a/crates/tdc-python/src/udf.rs b/crates/tdc-python/src/udf.rs new file mode 100644 index 0000000..c4e34dc --- /dev/null +++ b/crates/tdc-python/src/udf.rs @@ -0,0 +1,169 @@ +use arrow_schema::{DataType, Field}; +use datafusion::error::DataFusionError; +use datafusion::logical_expr::{ColumnarValue, ScalarUDFImpl, Signature, Volatility}; +use pyo3::prelude::*; +use pyo3::types::{PyCapsule, PyTuple}; +use std::sync::Arc; + +#[derive(Debug)] +pub struct PyUDF { + pub name: String, + pub func: PyObject, + pub signature: Signature, +} + +impl PyUDF { + pub fn new(name: String, func: PyObject) -> Self { + Self { + name, + func, + signature: Signature::variadic_any(Volatility::Immutable), + } + } +} + +impl ScalarUDFImpl for PyUDF { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn name(&self) -> &str { + &self.name + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, arg_types: &[DataType]) -> Result { + // Use the type of the first argument if available, else default to Float64 + if let Some(t) = arg_types.first() { + Ok(t.clone()) + } else { + Ok(DataType::Float64) + } + } + + fn invoke(&self, args: &[ColumnarValue]) -> Result { + Python::with_gil(|py| { + // 1. Convert ColumnarValues to Python objects (Polars Series) + let mut py_args = Vec::with_capacity(args.len()); + let pl = py.import("polars").map_err(|e| { + DataFusionError::Execution(format!("Failed to import polars: {}", e)) + })?; + let series_class = pl.getattr("Series").map_err(|e| { + DataFusionError::Execution(format!("Failed to get polars.Series: {}", e)) + })?; + + for arg in args { + let array = match arg { + ColumnarValue::Array(array) => array.clone(), + ColumnarValue::Scalar(scalar) => scalar + .to_array() + .map_err(|e| DataFusionError::External(Box::new(e)))?, + }; + + let field = Arc::new(Field::new("arg", array.data_type().clone(), true)); + let py_array = pyo3_arrow::PyArray::new(array, field); + let py_array_bound = Bound::new(py, py_array).map_err(|e| { + DataFusionError::Execution(format!("Failed to create PyArray bound: {}", e)) + })?; + + // Convert pyarrow-compatible object to polars Series + // We use the fact that PyArray implements the Arrow PyCapsule interface + let series = series_class.call1((py_array_bound,)).map_err(|e| { + DataFusionError::Execution(format!("Failed to create polars Series: {}", e)) + })?; + py_args.push(series.unbind()); + } + + // 2. Call the Python function + let args_tuple = PyTuple::new(py, py_args).map_err(|e| { + DataFusionError::Execution(format!("Failed to create args tuple: {}", e)) + })?; + let res = self + .func + .call1(py, args_tuple) + .map_err(|e| DataFusionError::Execution(format!("Python UDF failed: {}", e)))?; + + // 3. Convert result back to ColumnarValue + let res_bound = res.bind(py); + + // Try to extract Arrow capsule + let (array_capsule, schema_capsule) = if let Ok(capsules) = + res_bound.call_method0("__arrow_c_array__") + { + capsules + .extract::<(Bound<'_, PyCapsule>, Bound<'_, PyCapsule>)>() + .map_err(|e| { + DataFusionError::Execution(format!("Failed to extract capsules: {}", e)) + })? + } else if let Ok(capsules) = res_bound + .getattr("__arrow_c_array__") + .and_then(|m| m.call0()) + { + capsules + .extract::<(Bound<'_, PyCapsule>, Bound<'_, PyCapsule>)>() + .map_err(|e| { + DataFusionError::Execution(format!( + "Failed to extract capsules from attribute: {}", + e + )) + })? + } else { + // Fallback: try to convert via pyarrow + let pa = py.import("pyarrow").map_err(|e| { + DataFusionError::Execution(format!("Failed to import pyarrow: {}", e)) + })?; + let array_fn = pa.getattr("array").map_err(|e| { + DataFusionError::Execution(format!("Failed to get pyarrow.array: {}", e)) + })?; + let pa_array = array_fn.call1((res_bound,)).map_err(|e| { + DataFusionError::Execution(format!("Failed to create pyarrow array: {}", e)) + })?; + + let capsules = pa_array.call_method0("__arrow_c_array__").map_err(|e| { + DataFusionError::Execution(format!("Failed to call __arrow_c_array__: {}", e)) + })?; + capsules + .extract::<(Bound<'_, PyCapsule>, Bound<'_, PyCapsule>)>() + .map_err(|e| { + DataFusionError::Execution(format!( + "Failed to extract fallback capsules: {}", + e + )) + })? + }; + + let py_array = + pyo3_arrow::PyArray::from_arrow_pycapsule(&array_capsule, &schema_capsule) + .map_err(|e| { + DataFusionError::Execution(format!( + "Failed to convert result capsule: {}", + e + )) + })?; + let array = py_array.into_inner().0; + + // Ensure result type matches expected return type by casting if necessary + let target_type = + self.return_type(&args.iter().map(|a| a.data_type()).collect::>())?; + let array = if array.data_type() != &target_type { + arrow_cast::cast(&array, &target_type).map_err(|e| { + DataFusionError::Execution(format!( + "Failed to cast UDF result to {}: {}", + target_type, e + )) + })? + } else { + array + }; + + Ok(ColumnarValue::Array(array)) + }) + } +} + +pub fn register(_m: &Bound<'_, PyModule>) -> PyResult<()> { + Ok(()) +} diff --git a/examples/01-basics.ipynb b/examples/01-basics.ipynb new file mode 100644 index 0000000..2b9781b --- /dev/null +++ b/examples/01-basics.ipynb @@ -0,0 +1,433 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Basics: TrackingDataContainer and CDF Schema\n", + "\n", + "The **TrackingDataContainer** (TDC) is a high-performance, standardized interface for sports tracking data. Backed by **Apache Arrow**, it provides a metadata-aware representation that bridges the gap between raw data parsing packages (e.g., kloppy) and data analytics tools (e.g., floodlight, databallpy)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Why TDC?\n", + "\n", + "Our goal is to define a canonical, metadata-rich container that enables fast, zero-copy data exchange while remaining compatible with the modern data science ecosystem. TDC focuses on the **containerized representation** of tracking data, delegating raw format parsing and analysis to specialized tools.\n", + "\n", + "The main goals are to provide:\n", + "\n", + "* **Scalability**: Overcomes the limitations of object-based models for massive datasets.\n", + "* **Interoperability**: Standardizes column naming and coordinate systems across providers.\n", + "* **Efficiency**: Eliminates duplicated effort in data parsing and management.\n", + "\n", + "These goals could also be achieved by storing data in a standard `pyarrow.Table` or `polars.DataFrame` but these treat tracking data as a collection of generic floats and strings. In contrast, TDC provides **domain-specific logic**. It \"understands\" the data, allowing for abstractions that a generic table cannot provide:\n", + "\n", + "1. **Spatial Metadata**: The container keeps track of the coordinate system and orientation of the data.\n", + "2. **Consistency**: The container enforces a standard schema for typical data fields (e.g., player coordinates).\n", + "3. **Support domain-specific operations**: Standard operations such as normalizing player orientations (so that a team always attacks in the same direction) can be implemented as a single method call." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Why Rust and Apache Arrow?\n", + "\n", + "The core logic of TDC is written in **Rust** and backed by **Apache Arrow**. Under the hood, a `TrackingDataContainer` is a thin wrapper around an Arrow `StructArray`.\n", + "\n", + "This provides two main advantages:\n", + "- **Columnar Layout**: Arrow's memory layout is highly efficient for analytical queries and allows for **zero-copy** exchange with tools like **Polars**, **Pandas**, and **DuckDB**.\n", + "- **Native Speed**: By implementing performance-critical operations directly in Rust, we bypass the overhead of the Python interpreter, executing complex spatial transformations as compiled machine code." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The TrackingDataContainer" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Container created with 3 frames.\n" + ] + } + ], + "source": [ + "from datetime import datetime, timedelta, timezone\n", + "\n", + "import tracking_data_container as tdc\n", + "\n", + "# Create a small sample dataset from a dictionary\n", + "base_time = datetime(2023, 5, 27, 13, 30, 0, tzinfo=timezone.utc)\n", + "data = {\n", + " \"frame_id\": [1000, 1001, 1002],\n", + " \"timestamp\": [\n", + " base_time,\n", + " base_time + timedelta(milliseconds=40),\n", + " base_time + timedelta(milliseconds=80),\n", + " ],\n", + " \"period\": [\"first_half\", \"first_half\", \"first_half\"],\n", + " \"match/id\": [\"match_123\", \"match_123\", \"match_123\"],\n", + " \"ball/x\": [50.0, 51.0, 52.0],\n", + " \"ball/y\": [12.0, 13.0, 14.0],\n", + " \"ball/z\": [0.0, 0.0, 0.0],\n", + " \"teams/home/id\": [\"team_A\", \"team_A\", \"team_A\"],\n", + " \"teams/home/players/p1/x\": [23.5, 23.6, 23.7],\n", + " \"teams/home/players/p1/y\": [24.2, 24.3, 24.4],\n", + " \"teams/away/id\": [\"team_B\", \"team_B\", \"team_B\"],\n", + " \"teams/away/players/p2/x\": [33.1, 33.2, 33.3],\n", + " \"teams/away/players/p2/y\": [34.9, 35.0, 35.1],\n", + "}\n", + "\n", + "container = tdc.TrackingDataContainer(data, validate=True)\n", + "print(f\"Container created with {container.num_rows()} frames.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Domain-Aware Operations\n", + "\n", + "We can easily map the data to any other coordinate system or normalize its orientation." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original ball x at frame 0: 50.00%\n", + "Transformed ball x at frame 0: -0.00m\n", + "Orientation mode normalized to: static_home_away\n" + ] + } + ], + "source": [ + "# 1. Define the INITIAL coordinate system of our raw data (e.g. 0-100 percent)\n", + "raw_cs = tdc.CoordinateSystem(\n", + " x_range=(0, 100),\n", + " y_range=(0, 100),\n", + " origin=tdc.Origin.TopLeft,\n", + " length_unit=tdc.LengthUnit.Percent,\n", + ")\n", + "container = container.with_coordinate_system(raw_cs)\n", + "print(f\"Original ball x at frame 0: {container.materialize_frame(0)['ball/x']:.2f}%\")\n", + "\n", + "# 2. Define the TARGET coordinate system (in meters)\n", + "pitch_cs = tdc.CoordinateSystem(\n", + " x_range=(-52.5, 52.5),\n", + " y_range=(-34, 34),\n", + " origin=tdc.Origin.Center,\n", + " length_unit=tdc.LengthUnit.Meters,\n", + ")\n", + "\n", + "# 3. Map the data to this new system\n", + "transformed = container.transform_coordinates(pitch_cs)\n", + "print(f\"Transformed ball x at frame 0: {transformed.materialize_frame(0)['ball/x']:.2f}m\")\n", + "\n", + "# 4. Normalize orientation so teams are normalized based on their initial sides\n", + "normalized = transformed.with_orientation(tdc.OrientationMode.AwayHome).transform_orientation(\n", + " tdc.OrientationMode.StaticHomeAway\n", + ")\n", + "print(f\"Orientation mode normalized to: {normalized.orientation_mode}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABOsAAAGeCAYAAADFS4WRAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaHZJREFUeJzt3Qm8nFV9+P9DCGSDkIQEkgCGELYAQQQSlkDUag1ItVDpYldrtbWAKCoU7WpbK4o/oYJCXaq1dv+jSC2LpVbDKiA7JGxhDQQIZCGQBEPm//qc9IznTubeO3O3eeZ5Pu/Xa7g39w5zn3nmec7zfb7ne87Zrlar1YIkSZIkSZKkjhvV6Q2QJEmSJEmStJXJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkqURuvfXWcOyxx4YJEyaE7bbbLtx5552hDL7xjW/E9/PYY491elMK47TTTgs///M/3+nNUIn9xV/8RTzvBqJq5yzvlf2l4jv66KPDOeecE4rMZF0HFL3B47X5G/wtbfXv//7vYcqUKWH9+vXuEkVXX311OOyww8LYsWPj+bJmzZqO7pkf/vCHcTv42i7P+fL66U9/Gvbaa6/wpS99qdObInUl2tVWHgNpe4fzvP/lX/7l8OKLL4YLLrgg/OM//mOYNWtWqGKs3exx6aWXhrJ49NFHw1e/+tXwiU98YptrOo+//uu/bvr//cZv/Eb8/U477TSgv3vllVd2XUKChPVv/uZvxmvimDFjYlz/1re+NXz9618Pr7322rD93fvvvz/uq04kq+677774nvfYY4/4nmfOnBk/e35eRv/5n/8Z3vjGN4bddtstjB8/Puyzzz7hV37lV2LMnjz99NPx8xhMB8ZQH/8phv///r//r+nv3/Oe9wz4XO2EpUuXxvfDPVKn74/68kd/9Efhi1/8Yli5cmUoKpN1bahag9dqw9Ls8Wu/9muhLLiA//mf/3n44Ac/uE1DeeONN4bjjjsuXhCmT58ezjzzzJYTes8880w499xzw5vf/Oaw88479xvst/q3Nm3aFBsfjs9x48aFo446Kvz3f//3Ns/7u7/7uzB79uwYrPzWb/1WWLduXY/fb9myJbzhDW8If/M3fxOG+vhofIyEN73pTeGQQw4Zktd64YUX4sWf/Usjz40Q1Qvsq8svv7zt1zr77LPDAQccEC9qfB6LFy8O3/ve90KZkTzis+f4LDK28Ywzzuiz8+S2224LRbXDDjuEj3zkI+FTn/pU2LhxY6c3R+o6tO/5I1UwNf587ty5oSgeeeSR8Pjjj4ePfexj4fd///dj7Dp58uRQRZdccsk2n9Vb3vKWUBZ/+7d/G2M5YslGxBT/8i//ss3PX3755fDd7343/n4wyYpPfvKToVuQ0DzyyCPD//7v/8Z7N2KQP/uzP4tx3O/93u+Fz3zmM8OarGNfjXSy7tvf/nY4/PDDw//8z/+E3/3d343vmffKPuDn3/nOd1p+rT/5kz8JGzZsGNB2cI/B/zvcHQaf+9znwjvf+c4Yl3384x+PHRXvete7wkMPPRT+9V//tUeyjs9jsMm63o5/3iv7q8q+9a1vxXtV9JaALIJf/MVfDBMnTix0h/boTm9At6DBe/e73x1vpGnouDDS6H7ta1+LByGNwCmnnNLSa3ECk6QZaINHIoxkYVGQNJo/f36Pn+29996hTL00DzzwQAx4czTyBHwE6J///OfDU089FS8UXBSuuuqqfl+X1yQ42G+//cK8efPCTTfd1Otz2/lb9L5wTH74wx+Or01C4e1vf3u8OJPsw/XXXx/+8A//MH529Dp9+tOfjgkjEnjJV77ylbB27drw0Y9+dAB7LcRtJSjOcfEk4fnHf/zHoduHF7300kvhr/7qr2KvbEKy7tRTTw0nn3xyS6/DMcDn+vzzz8dAikCSHqh/+qd/Cu94xzvijdb555/f0mstWrQoBgg77rhj2++HAIr/l8TOSOE90k7ccsst4eGHHw777rvviP3tquHY4przz//8z+G9731vpzdH6iokunI333xz7ABr/HmjV155JXaudcJzzz0Xv06aNGnIXpMED51S3YZr8tSpU0v5Hqmg5Fr6gQ98oOnvif24f7nrrrvC61//+vrPSdS9+uqr4YQTTgg/+MEPQlHUarXYqUQCbShxzrKPjjnmmJhkoYM8IVamw+3ee+8N3aav45WEPfeMxPhLliwJ06ZNq//uQx/6UDj++OPj7+++++74nP7+xujRo+NjILbffvv4GE6bN2+OMTmdKd///vd7bRNHwmCS4GXAeUy8+eu//uux8pc26n3ve18oolGjRsVrxDe/+c2YfB2pApK21NSvhx9+uDZ+/PjagQceWHvuued6/O7555+PP58wYULtkUce6fN11q9f3xV7+9FHH61xaHz961/v83n/+7//G5/3H//xHy2/9k9/+tPapk2bat3kne98Z+24447b5ucnnnhibcaMGbW1a9fWf/aVr3wl7pNrrrmm39ddt25d7YUXXojfsw/5/9inzbT6t3784x/Hn51//vn1n23YsKE2Z86c2jHHHFP/2R/90R/V3vzmN9f/zWc9ffr0+r9Xr15dmzp1au2yyy6rDaWDDz649sY3vrHWCfxd/v5Q+Id/+Ie4n2+99dYeP6cd+J3f+Z2WXuPVV1+tHXLIIbFtufnmm3v8bvPmzbVf/dVfjX/jX//1X/t8HT7f1157rdZNli9fHt/bt7/97dq0adNqf/EXf1ErKrbz9NNPb/o7zptmx0ER/cIv/ELt+OOP7/RmSF2P9qAxfE7Xl9tuuy2eZ+PGjat96EMfir+7/PLLa29/+9vjNXzHHXes7bPPPrW//Mu/jO18s9e47777am9605via8ycObP2mc98Zptt+MIXvlA76KCD4nMmTZpUO+KII2r/9E//FH/HNYjtyx/5dfd//ud/YkzDtWeXXXaJMc7999/f4/X//M//PP5/bMu73/3u+DcOO+yw+LtZs2bVTjrppBiv8HfHjh0br2UpfiFu4N9jxoypHX744bXbb799m+1funRp7V3veldt8uTJ8Xm8zne/+91tnnfvvffGWIW/sccee9T+6q/+qva1r30tbhuxal/SeyBO76v9/uEPf1j7wz/8w3gt4n3iscceiz/bf//949+eMmVK7dRTT93mb6bXuO6662of/OAHY9zEPv393//9GOsSS/3Wb/1WfF0eZ599dm3Lli09XoPr9wUXXBA/T/bFbrvtFv//F198sdafH/zgB/X30CyOJxacPXt27Zxzzunxe47Hd7zjHfFYIW5pdOWVV9aPkZ122ik+n88iaXaM5edEq+8pHUtXX311PAZ4Lv8fvv/979cWLlwY9yfbyGfx8Y9/vDYQJ5xwQm306NG1xx9/vKXnt7v9fP7z58+Pz2V/EyM2HiONjzze729/I31W3JNyT8DzfvEXf7HX9/AHf/AH8e8sWbKk6e9/9KMfxd/zvFbO+/S73CuvvBKP+1133TVuD8fUU089FZ/H8xv3QX7+tLLv2vHMM8/Ev9FfPJnuXRsf6Z6X/cW5vtdee8X2es8996x9+MMfju+11eO/8f2D/fLe9763fh3Ye++9ax/4wAfq98T93VP3dq5+8YtfjMcpr8lrn3baabHdaXZtueuuu2qLFi2K1w3uC9Pfov1YsGBBbOs4z/77v/97m7/D9v/u7/5uPBf4W/xN2uJm+Ex5L7fcckvt3/7t32qjRo2qPfnkk/Xfsx38Pm/zuXbysze84Q3bnLsLFiyo/7uV6+mf/dmfxfO9MWeD97///bFN4d4pYTv4282uVUXgMNgWUNlCD+mXv/zlHj0ToLeOaiR6Hj772c9uM1cGpc9klhl+kKqams1ZR1ULVU68Hj0+lPGuWLFim0kqm81ZR3XKL/zCL8RqqQULFsSMPr0kZIlzzF1CpQ5VXFQ3UfZ54oknxh634ZDmzKAC7MILLwxz5syJFYHsE3r0KD8/4ogjwi677BJ7bejlofqrt9dguCHvi57qt73tbeHJJ5+M2Xt6Uvbcc8/YE0c5K++zEdVnvD5/h/170kkntTR8mR4+5jnIq6fAkNHUs85+TH77t3877lvmuOsP20GlZn/a+VtU1NF7lVcBcjxQDUrlHvssHW/5kBi2g2M84ZjjOPmlX/qlMNyWL18e59ZhG/hsmezzv/7rv5oOqf23f/u3OC8LpdV8lpwn6T0Nlf6OFYbT/s7v/E78nopStotqRr7SDvzDP/xDfXgvP+/NZZddFntyqXhqHArKZ0i7QlVEfv6n/UAlLxW6DMlnn3GM9DZnXTpvOD9oH6677rr4Hnj0NWddmh+DdohKQb6n/aMNGezcLvSycfyxb+nR4t+5L3zhC3Ef5PNc/L//9//iNjKkM2E7+IwY9p3QVjCp+q677hrfM21MYwk+85nkVQY5hiMzDHkwqFRIxxCfIe0S83fk0nXgwQcfjOc27SD790//9E9ju8ZxncrzOd55/82GvDNEn6pE2lbm4WGiXH7eiN5mrhHN2kdJg8eUBsRUzGVKzJOGJdKu0n7SdjFkkTaJ+KfZCIvVq1fHaifaJ875Aw88MLZveQU9Ve/EiwcddFD8O1QD8Dd//OMfx9//wR/8QX3+Mp5HhXuqZr/22mtj+0aVCW0Q28QUGwsXLmw6RI9rM7EBVePvf//76z+nGprYlgpwKvPZbr6nLT/rrLNim8Z2Ud3DlBFMq5FwPeU6T5vIPuB90lZyncmH5TGHEPuQkQU8jwoo4lr2YTto81atWlV/sK2NizMQl+afCdXz7BdGsnA9oiqLoYRcN/NYKWGaFEY68J6JS7hfoC1nn3CdYv9xD8D9ROOIAz4vRjbwGfDeqIRmP/I5UTnXF7aR6whTlvSGUUHEDFtzCCHuAyqP+PyaYfu4NnPMMvqD98H+YfvTMcI2NxsOPpD3xAgDtpHX47kcyxwj3NdwLfvLv/zLeIywX2+44YbQLj4vPjtGH7zuda9r6f9pZ/s5F4hj2H62k9iG+CnFjfxdzkNwXjYOm29lf+fVY2wD87ER6zDEs69RQdwfEos0w3bx+8Z4u6/zvhHv86KLLooVnGw7MRfvpVX97bt2sE/4+7zvvuIc9jvHFLhXSp8H+wP/8R//Ed87o494b+xvvnLPlfR3/Ddi2C3xN+fhr/7qr8Y2harGH/3oR9u0J4zaydur9GgW19GGn3766XHaI/YfxwP3DtwjNx6ntHucU9xvkK8gZqR9476Kr3yG5513XryP4TNhO5Jnn302ttlcP5gWhnOCuJN7S65BjThXuOfnHok2kPuUfDg+UxIRG1PxmXBvQpUbOYk0LRPXjRtvvLH+2bR6PWXfcq7w3nLkHrgfYD/l1Y+8BgbSvoyITmcLuwE9m2TA+8Lvyb4nqQeCzDM9H1/60pdi9jv/Xe5XfuVX4s/ogeN5/Pv1r399y70TBxxwQG333XevfeITn6hdfPHFsTdzu+2269EzQ/UHmfRzzz239nd/93cxE01PJRnmFStWDLiy7u///u9jz2X+oFcqvQ77gMz3eeedF3uq6NniOWTFP/KRj9QuueSS2mc/+9n4HnbYYYfaHXfcsc220LPD63z+85+v/cmf/EnMph999NHx/R577LGxl/nMM8+M75nMf+6b3/xm/DnZ+Ysuuij2UvN50WPUX8/s9ddfH//+FVdc0fTn9Bg0oneM/d+Ovirr2vlbb33rW2tz587d5nnXXnttj/fxj//4j7EHj6q8Bx98MPa08P+CHjV6uOj5GGqNlXUrV66Mx+3OO+9c++M//uP4+XLc0wtD1VXjsTZv3rzaoYceGp/HcZx6gfIer8FU1rVyrNDbSw8r28M5xL688cYb41f2G1UVfJ9+3ptf//Vfj69BD35vUu/dQw891GM/cC5wTrAfPv3pT9defvnl+u/yY4h2h5+xTZwjnG9UCNAO5J9Ds3Oev83+ZZ/RG8h5SiUEz+N1B4Nq5N/7vd+r92KmHriE3i1+9p//+Z/1n9GOclwceeSRPdo0nve9732v/jPaYXoWaQfZP/TINT4nVaXec889PbaLbeDnHAcJ/2ZbG9s4HhwjjZV19EjSo8dxSbv2yU9+MlZbUEGStzfpOsDnSA82+5ReZn7GdtMeUtnBz6ku4Of0hCe0sW9729vieUyvL236GWecEf92s9721I7k+1TS0FXW8bNLL710m+c3uz5RzcK5u3Hjxm1eI29/qLqg6p22N+H87u9a1luVBu0NlRGpqh9c62lbf/u3f3ub9om2qRExJ7/Lr2/EEvyMio28eol2qfG69Ja3vCVey/P3TrUZsdx+++1X/xntGv8vIwYSKiWIWduprGt8sP15PE0c1Vjl2Owzu+mmm7b5fNJrLF68uEfFHCMZiCWonEn4G1yf8mtvqkBJVZEJlWbNft7oN3/zN2NVU6O8so77gFT9B+4xqIIibmis1nnppZdivEP1SY5Yjf2e/7zZedDue0rHEr/Lca/QV1VkO1IVT6p07c9Atj+vXuMYJRb86Ec/2m+M387+TvEgsW9/1qxZE5/bV+UdqKrleYz06e+8b7x3/clPfhL/zXmae8973tPyvWsr+64dVFTxmhzTVB9+6lOfitvZKMWOze5zm537xNmcz3nb1tvxj8b3T9tKG9tsFEZqN3qr+Msf+bnKvuJemDgwH11D7JvuzRuvLf/8z/9c/9myZcviz9iufHRPasvzfUMMzD37qlWremz7r/3ar8XjNN9njBqiTeKeLr/f4d4uR7ybV8z90i/9Unxsv/32tauuuqrHvcB3swq8Vq+ntMFHHXVUj+dxX9nbvTb7kpi7iEzWVajB4yBuHC7H6/A8kg4DTdY1e/Aa6XUmTpy4TSkqQUvjcFjKdknckBho3BaGJ/BZJJTC83NOfobWJuxvTrh0wrZzIWzmq1/9atOb+nThbVZe/su//Ms9hpQONlnXzt8igP+5n/u5bZ5HAi6/kWD/0yimz4ty77vvvjv+joY/DzCHM1mXgvEURKbPjFJ4kmTpeE3HGsnldI7h3//93+PP//Zv/3bQybp2jpXehj+2MwyWmyZety8kbfIka9oPJL8bL1iNyTrOLy6YDC/Iz5FvfOMb8XmtJOtSQjJHiTrDVQYqlbqnMnsCFW5g8kCaz512Iw3d4Tm8F453LuR8Vmn/EGjkJf+N+yUNN87PC9oSEpEMB8+R8OczzKcs6C94ajwO2r0ZJvHbeDNHUEjnRsL74yY4P7ZIBvOa+bkDznFe94Ybbujx86effjr+vNmQOkmDT9YRS/U3zQfXLxIQ3/rWt+Jr3HnnnT1egyRK4zBJ4st8aBDtANeOvIOjlWRdagMah0SCZBOdCo3tU95BkMecdBg1i5W5Acvx/vh5Gi5Fu0j7xnDWxs4POjZ4LsOtQIcHnbKN6IxpJ1nHsFyuN+lBx0V+He9v2B3XEG5S2UZihDxWT69BLJJLsU1jjHDyySfHeCu/5vBZEiM37g+Ohfe97319bhsJiX333bfPZB3o5GS/gc6f3/iN34jfNybr0s0sw2sbt4fYMP9bvSUr2nlPHEvEe43SfiUGH+w0Hyn5Rkd/K9rd/sZzIe3vU045pd8Yv539nWKyVobyMuSQ55LM7QvHQX7O9XXeN967kgjj33T459I9bSv3rq3su3aRkCIBT3yUYjTaz3yof1/JuhyxIJ9FGjLMEMx2k3Upnu0vj5DabBKOeXuVHhwP+bnK++T5DKHOcQ3i7+UdPL1dW2jPGu+LUlv+p3/6p/Hf/D88j1i18RhNn2tqU/MhpXmxEJ3EjT8jxqVzOcXbxM2c79xfUIgD7u222267HvF0q9dTCgz4GcPGE/YJ7W/jfgD5B+4xishhsP1IZaD5ZKTNpN83rqjZ26SvubScNKX4jWX1rWI4RF7qzHAqhnMxxDCh5JUSU1CWz5ANSkl53u233x4GivJThmnmj7QCDCg3bRw+zBC3NBE+Za6ULFOyygT7zbaFkmyGiSVp2CBDLfIJT/k5Za4M3QPbwlA6SuzzcmL+Ps9tHHbbiH2ExlXU0opIzRb6oLR2oCsmNdPO3+L73p6Xvxbvn2GYDNtgYl2G4jHs9YorrogT/jO0mH1I+TLl1XyljHuoMdEvpeFpiDg4JilNp/yfoQA5ytDzc5FS7RkzZsTXGazBHisDaVsG2q4wDLe/CZj5XDl+GcKQnyOsgtbOqoCNbRjtTN6utIvy+N13370+RIwhPAwLYHhAGl5LO8VQ1lQiz3Ap3gtl7sRBaTEWyuZTOX2S7xfK/lkkhW3O2xXaEoaYUpafhgbxtymZZyhW44TNPLexjePBMJnGFZ4ZssUwjnyI+6GHHhqHTDQ7TvNJdznWaAPZJoYXJLy/xvacoRoM52CYXH68/tzP/Vz8fePxmj5zniNp6DEtQbMFfhjSxQJktDsMayceSotT0D7lmNKjcZoUzt186CbDYrlOcu1kESmGQbUyfIfVYUFb0oi2hLaBIVA5FlNrpnE4YYrPGIrf7Odp+xn2RvvGUD/2Q/5gSH8+ETzby/tr1Gz7+8IQKqYySQ+GNvb3HomViG15P8RUTFHDNhIjNH5m7e6P/LMkBuP1GMLXuD/Wr1/f0qT46RrWF4a8cs1g/zOsrLchsGwPuI40bg9DZ1vZnnbfU7P9T0zA58T1kXiBYXpM+ZIPp25Vmj4mH9Y3lNvfbGht4znb199qZ38Ty9FG9CfFjv29597ucXs773Ocn8Rqjc9tZ7Gwwey73hDDExvyGmm49x133BHvY5jaqD9PPPFEPYZL078wdQqanfv9YQE5Ynhi1VZwL5a3V+nBvU4r7TnXIKa+Sb/v69pCe9Rfm8320+6lqcDyB8PDkR+nrALLMUG7SXvDgyGxDIXNp7whLue+n3ieofC8Bj+jvebzA18POuigHvF0q9dT2hC2If1Nfve9730v3gM1W0SCdrSQi0u4GmyoVIPHRY7x3SxPzOos+bxTzO80UKlh6U1v+4C5vRhjv2zZsh5j65s9f6CBYX4hbCafA66dYCglBJrNIZCvZEXisHHuBBqWdlZFavVvpef29rz8tZodY2wrK78SMBOY0mhycWD+B+Yx4ILXOB/aYHHsN87XhjSXB7/PL3CNgTsNK+8hzetBIMUjYT83Jop7M1THSqtoK/pLnAy2XWnWjhDstbpaM0nexv03mECKNoekHIk62qCEY4C2gHllmGsDHH/Mx8FNExdsjsXDDz88zuXEv9McbMyHlONi/Nd//dcxaZafC40XYRK/JOd4LYID5uJgXg7mumhEkNOsjWNV5nZuhq+55pptVm9r1rax3xtXL+TnqfMgHa8kMXs7vhuD/NSGFTUYkbpdsw4UbnK40eP6wTxJ3LRwftN5QNKtMfnQW2yQxyC0Jdzc0NbR2UvHG3EdySXmTBvu99TXdva3/en9Mvdpb3ODjvTK4M3eI53lX//61+M8eawgSvtL20nSqFnCqJ39kX+WvBZJocZ5W5P+4hdi91auxyQwPv7xj8fOO/6fdJ1tlN4b82/lne5JK6uBtvuemu1/fkZnHZ1OzKnGcc71mviMBEw7MTTHE9t9zz33tPT8dre/lXO2r7/Vzv7Oiy76wvFKzMRKr33h93QyNMa3Q70ab28Gs+/6w3siTuSxww47xHtO5vVMibfeYlSez30b7TOdocRrFC+QwBtIsrgoBttmkxBL83U3okMaJCW5Z+Ses1lHCyvEfupTn4ptKR3TXAs5z4mDOef233//GPtzPSN+Jz4/5ZRTBnQ95V6FOfo4j7k2Mlcdr9nbKu68dqurho+0ga3BXCFlavCYKJTezPe+972xcopMNY0+wchwNkDN9gGZdxo+qlioTuEk5T0wUTETEg91IzPQwCMlMQmG8t6s1MNBJU0jfkY1GujBTBVECUmKVpMl7fyt9NxUVdj4POTPbXTBBRfE/cHkoUxuTyIkbSuTkdJTQ3KilV69TmHC3fxmZdasWU0nzR6uILUd3HCRUKIXr7dJj1O7Q89SkdqVwSy8wLFIwo5HIy6q6SaCakuS+PS6ccFOlcN85d8k+enxyyuK+TmTUJN842LP+UCQxk0XQUKOG0V67GmLeD5f+dz76ngYqX3cSnvO8UpHyec///mmz23syEg3dEUNRqQyooOLJPu3v/3tHpNk550VA8ENJJUDPOhoYzEoboJIyOQTZ+e4HoJEXyPaU9qGxqrioUYcAdrl/tpatjd1ouWabf9Q48aOG9N8YR9uQPNFj4YCN5t0FFFFNpDrOskErptUjeSjTxoRY/A3OB6ZOL+3eIbtATF5f59Pbx0/g31PCfcnb3nLW+KD6xz3MCyUQgKvnes0FT0k+Yg/iG0br43Dtf2t7qtW93e7SFSwGA2xfD56JY+XiI9ZLGEgOD+JQ2jL8sQMlVRFQ2KIZF26F+rt8yChy0gjnpsvKMFIikatdnyS3CU3wIJyQylvz1O7Cq4HfCZDdTyx/RQMkMjs7zW5ztFOXnLJJdvEmmwnC+NRBc7xSAVgWvSO9imP8Umq0a7Rgb4ou262ez3lM2RkDAsG8XosxHPwwQdv8zzum9lvqVCkaBwG22KDx4FAg9dMavB43mAbvNxQN3gEHySOvva1r8XeQW6KOfGGOvhodVtoXDjhqGThxpltaaVEuR2NF8LGR74iZm+BEBo/G6q9CHYYapjjZCcBw2pWoAqoryHCrWj1b4HvudA0DptMq8Tlz81xAaMaKSXs0pDXlNxLX5slAgeDY7+3G4f0+1xj4E7ygvMkJT9pmPN93VvP6HAcK+1WLaX2onHV5oTP8Lvf/W48BgdSaZD2XWM7Qtl5qwnMocbnwf5lOE7jg55/VgJMQ7W5iHMxp33Nk3VcoDmeqcJL/06oMOFGlQo2OiVYmbG34IKEGNWitEUksi6//PK4DYNJUI7kzTDHK72/3Mg0O14bq/tSG1bUYEQqo9Se5Il2rt10JgxUXmEL2kk6dPgbfa0eSucFMQA3onncx00k1UqsBjjcaP+5lrJiYbMOSDpgErbn5ptvjlNz5L9v57o+mM+tsbqHFSEHuxJ6IyrDeU060Btxre4vPqfqj+38yU9+0u/fIsZj5ERfU+wQi5NYIDHW7FjKP590LWvcxsG+JzRbzTPFr81Gj/SH981+4n4jH32RsP84L4Zq+xv1tq/a2d/tohCCZCPJuMY2g/3LFCckMhun82hVqoxtbMs4TzqBVVXTFCmN0mraKS7q7fNo1l7zfbMVqHt7jWZJZwpTqDhrvI9r/FvtIM6j7Wdl2fw1uMcned/Oqrx9YZ8wnRXxdbOEY36M0unNvT3HFtMU5Q+qqRlW3DgUlnieBHyK8YmTiVNZXTg9Z6DXU+4BeD1ei5V3e6uqS+0n0+8UkZV1LaAh4wCkwaNcMx8yOlQNHr1FHGwkS4arwWsWfHCTTAJmpIcd5CdcSnBwwtLQtrq0eivyCyGJSnpzGxuZvoYZsJwzjSENLBU7CT2YNJQcF1QrpmGKVGURCDDHXirDHWzvRqt/CzSIVJcxtwANYwpsqCxiqGFvPYrMBUbS44QTToj/puIoJRgob2a4HdpNNPaHYJxlv/ncCTrBMEG2nwRcY0UZiS0qB9I+INFCwE/5M7hI5D1MI3mspIt3q4EcnxWVEAwxZr/T85eQvKf3myTSQG/qeD3aKnpWmVci9aRzoRzMfCADRRKO5DzHK++9EQlh5pBj3kSqRUi6sew7P6P6MO9147UIUEhY5fN40K7QnuQ3VCQmScQ1Q+BOm0vbzrnU24W8VfnNMMdpmksv3QwP9vVz3FAwBx6fL3M85tg/HEN5YpBghH2TzjNJw4/gnziAKq0zzzwznoNcuwczzIuOVq7FVP5wreb6fPHFF8ebs/7mQT3//PPjDQztAPNi0lYQaxJnMO3ASPjiF78YKyuoDGZYJtdsKiiIA6jev+uuu+LzzjnnnLivuD5+6EMfiu0ZsQGdIv2NdhksOtP42+wX4hC2jWqrwUwZ0wxDurj+MKqEzlc+W2IPOiaJz0kSNLteJuxHtolt620Kj/xv9TUEEMRAVMVwbWTaCTr2iXu4BjMclWOOYy3Fx+C4Jn7i+svzB/uewBA37rc4pvm8mdaBWIiRHXmVGIlfbsL7O584DznumBucDlDeH9VgTDVCtQ5xB8nMtJ8Gu/2NiAvYPyQNSKQwnJXPi+R1q/u7Xbw/YhHm6OJc43xnChViIhI6TMNCfJU6qtvF508ShxieZODRRx8dPwsKBoZ6yo3UId9XRzPJOj5ntoM2g/sd4nHiPzp8SZhRWQXeM/HZpZdeGttM2hbukTg2+B33T9wbcz6QpGoWM/d2/DfDfQUxIMcW8RrJKO5dOJ4oBMrnXW4VxwlxJqOJeL/co9JRzHlC7DyU8Sb3KSTU2Ee02bSJ5D8Yfkrbw/cUefAc9kczHPPsJ94z8TvnFPE890FUvOZJOe5H6dDhc98zG83V7vWUv8FnwjnE50OHfDMUd5B7SMdH0Zisq1CDR/DBBZAbdw54yn25cR9ocmOw28KNO2PRuRhT9UGjSQPQrNdroNoJPJohYcCFmsaIfZejgWE/psaXIJMhEzw/Jb36k4IDJswEjU6q4KRcuN2/RUNKMoQGnOCGJCzHbjpWm6HXmrlA8uCXBpJkD0OVOd6/+tWvxtfOK91auXj2hyQh5w43DzS8DM1mezkeuEA2zs3B7wnUOIYJ7jlneI9cPFpBwi3t8xznM+f3YIMmzmWOFYZskHzidZvNyQeSwCQbqYxK74l9TnDBkE0ugswh2NvFvz+8Pjdf9KITFJLc4bP6xje+EduqoQykOE7S59bbEG+CYQLjPOmdo91jf9MmkawDF2+CBG6YaHtBcEvvKEEJfzdHW8K+55ygao5zgACdY6TZzR0XZipX02INfO6DNVI3wxynTLhNZxEBEscnSUoS7Pyc6sI8AUwwwnOG+mZTUu8435hbjracazo3GtxE0e73Nmdbf0gk0E7S1hEvcTPD9TOPGXpDxx/zf1FpxDw+3MwQV5BEaGUu1KFAnEcHKDeZXI+Ie2nXaY/Zprzzg7aNaxjXAfYl7R3X1nwBnuFAQoabO/YzIz5oO7m2D/Qz6wuxL7EDN6ef+MQn6vPKcpw0LobR7DpP7MI1jITAUODayT5mn3M9o8OXaX64HqcJ5cHQaz4bprSgM5kb5hSvDOY9gTiBeOXv//7v4z0W1TEcpxwz+XBfjv9WO5E5b0hiEDvT8Us8SKUP1306tPPkxmC3vxHbyGuSAOTY5VrNsc1x3+r+HgjuB0hA8XfT/SrnER3SvK9WFz3oDfuR90Ycz8gI2hfuJ4jRehuOPxB04vdXVELCi85L4nU+z5UrV8ZzmG1hv+ZJpDSHHfdKtClUTPL/EFNSAcdz2We8B+5TmR6IkVK5vo7/RnyeFKRQcEGbwsgZfkasSLHPQBFTEjdzb3LWWWfFeyTuEWkLGgsOBoNOIe4VuQ/m3p2EIMcRQ0pTBRz7gU5iFvLoDb/j3o5KR85x7mv5jNgH+f7l2OfcyxN4A72eMuKK/cNzGhfpANvMNnFeFnZO504vR9tN7r777tq73/3u2owZM2o77LBDbfr06fHf99xzT69LXLOkcG+/y7388stxGegpU6bE5ZVZ3v2BBx6Iz2N54/6Wvz7ppJO2+Tss1cwj2bhxY+2jH/1o3P5x48bF5dtvuummbZ6Xlnzvb0nrtMw0S5I307h0fI5lk//mb/4mbvuYMWPistrf+9734tLk/Ky/1+jtb6f9w7Lcjc9fvHhxXI597NixtTlz5tTe85731G677bZaf1hanaWjn3jiiaZLwh977LHxNadNmxY/Q5aSblVaVrzZY6B/a8OGDbWPfexj8fhk386fP7929dVXN/37fA5HHXVU7SMf+cg2v2O560WLFsXjka+PPPJIj99PnTq1dvTRR9fawRLh+bEGXvfUU0+NS4Pz3hYsWBCPhWaf97/8y7/UPv7xj8clvjmGOe5bWcYe/N3e9vVb3vKWto6V3o6zZcuWxX3FtvF7juf+PPfcc3H/77vvvvHzYj+89a1vrV1xxRVtnXPpd3zNfeELX6ifZ+zbG264IS6NfsIJJ/R5zrPt+TLxfbVfLIfOe169enWv7/Md73hH3J+0db1hP9O2rlq1Kv77v/7rv+LfOvHEE3s8733ve1/8+de+9rVtXoOf7bfffvH9HnjggfE9Ndvm5LOf/Wz8He1RM/yOc62Z3o6Da6+9Nrav7JOJEyfG937//fe3dI3obb9z/HL+5F599dXaZz7zmfhz3u/kyZPjZ/vJT36ytnbt2vrz1qxZU9txxx1rX/3qV5u+D0lSdyKG4rrJdadKiH9Hjx5du/jiizu9KcrccccdMbb51re+NST75b777ouv13hfILXizjvvjMfPN7/5zaa//853vhNj9aeffrpWVNvxn04nDNUc5df0NJKxp+dMnUEPGD3BVCY1m8Oiiu6///7Yo0IPx1DNi9AXhinQE0jvcbvDD7RtLxI9cfQK0gs5VL1u9F7Re9ltqKCgR5Je/KEcgl8kVKCySAyL94zU4iSSpJHBtBnMT9tsIvyyooLq9NNPj6OQqDDUyGPkQGNMQXUao4SIqfpbzKMVjI6gGo0F+6R2URVJFSWVls3mjGYUDBV8xMhF5TDYAjd43GAxDDCfQF0jjxJdSn8JhpgbjbL5qqN8nwZuJBJ1GjiG7zBPRF7azbAF5pdoZcGMVjCEm/YrzRvYTeirYmgIw2vKmqhj0mqGyzFkwESdJJUPU3hUDfGnMWhnkeBgPlw60xkqzPBGHgzFHIpEHUjI8pDawXBmCkuY55SEXW+Lu/W2KEmRWFlXEMzD0FuDx7htqcqsrBv4fqNqjHlLmOuBefBITjE/G+1NVXujmf+EOfRIOlNdyKq7vc2lJ0mSpJ6o5OT+laQI8wfS6cl8uiyamBY1kzph7733jnObM5cdlZ79Lb5UZJ5JBcEkizR6DLNMDR4TR9LgSdJAL1b0brLyEtV0TD7LcFUmMq5qog5MLM2kzkxIzCTLJuokSZJa9/M///PxIRXNY4NY/LBorKyTJEmSJEmSCmJUpzdAkiRJkiRJ0lYm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUEKMH+wIrVqwIy5YtC5s3bx6aLZJUSRs3bqx/P3bs2I5ui6TuNnr06DB37twwc+bMTm+KJEmSNPLJOhJ169evH+zLSFLTxJ0kDcTSpUtN1kmSJKmaybq8os5qGEkDZWWdpKFsS6z4rzZHfkgaCONRSUUZ2THoZF2eqFu8ePFQvZykirnmmmtigGRbImko2hJVmyM/JA2W1xJJnRzZMWTJOkmSJKkIHPkhaSCsrJNUlJEdJuskSZJUSlZrS2qHIz0kFWVkx6ghfTVJkiRJkiRJA2ayTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKgiTdZIkSZIkSVJBmKyTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJklR5mzZtCmvWrIlfJamTRnf0r0uSJEmS1EHXX399uPDCC8Pll18eXnvttbD99tuHk08+OZx11llh4cKFfjaSRpyVdZIkSZKkSrrkkkvCokWLwtKlS8PnPve5cMUVV8Sv/Pv4448Pl156aac3UVIFWVknSZIkSapkRd3pp58ePvjBD4YLLrggjBr1s1qWM888M3z4wx8Op512Wpg3b54VdpJGlJV1kiRJkqTKYejr3Llzt0nUgX+n3/NVkkaSyTpJkiRJUqVs2LAhzlH3/ve/f5tEXcLP+f13vvOd+HxJGikm6yRJkiRJlbJu3bq4mMScOXP6fN4+++wTn8fzJWmkmKyTJEmSJFXKxIkT46qvjzzySJ/PW758eXwez5ekkWKyTpIkSZJUKePGjQsnn3xy+MpXvhK2bNnS9Dn8nN+fcsop8fmSNFJM1kmSJEmSKofVXpcuXRrOOuusbRJ2/Dv9nq+SNJJGj+hfkyRJkiSpAI477rjwpS99KZx22mnh2muvjYtJMEcdQ1+pqCNRx+8XLlzY6U2VVDEm6yRJkiRJlfSBD3wgzJs3L1x44YXhYx/7WFxMgjnqGPr65S9/2USdpI4wWSdJkiRJqiwq53hcccUVYfXq1WHy5Mnhne98Z6c3S1KFOWedJEmSJKnyxowZEyZNmhS/SlInmayTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIYPdgX2LRpU/y6cePGcM011wzFNun/jB49OsydOzfMnDnTfSJJJbJixYqwbNmysHnz5k5vSukQj+TxiSRJklS5ZF2tVtsmQNbQWbp0qck6SSoZEnXr16/v9GaUWh6fSJIkSZVK1m233Xb1gHjs2LFDsU3KEp9WXUhS+eRtu9fO4bl+Ep+ouhz5MTwc9SFJ3c8RHt0xsmPQyboxY8bEDeNmY/HixUOzVYpDiq1UlKRy89o5fNdP4hNVlyM/ho+jPiSpuznCoztGdgw6WSdJkiQViSM/hp6jPiSpHBzh0R0jO0zWSZIkqVQc+TH0HPUhSeXiCI9ij+wYNWSvJEmSJEmSJGlQTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQbjAhCRJkiRJ0kBs2RLCpk1bv65bF8LEiSGMGsVqR1u/SgPgkSNJkiRJktSujRtDuOGGEN71rq1Jupkzt3499dStP+f30gCYrJMkSZIkSWoHibiLLgph0aIQrrpqa2Ud+HrllVt/fvHFJuw0ICbrJEmSJEmSWkVC7tZbQzjnnL6fd/bZIdx2288SeVKLTNZJkiRJkiS1ijnqzjuvtefyPJ4vtcFknSRJkiRJUquolLv66taemw+RlVpksk6SJEmSJKlVrPraagIurRIrtcFknSRJkiRJUqtY8XVUi+kUnsfzpTaYrJMkSZIkSWo5kzIqhBNOaO25J57YemJP+j8eMZIkSZIkSa0aMyaEc89t7bk8j+dLbTBZJ0mSJEmS1HImZVQI8+eHcP75fT+P3x95pJV1apvJOkmSJEmSpHaMHRvCGWeEsGRJCG9/+88Scnw96aQQrrtu6+95ntSm0e3+D5IkSZIkSZVHIm7hwhAuuyyE117buuprWnyCoa/OVacBMlknSZIkSZI0ECTkUvXchAnuQw0Jh8FKkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKgiTdZIkSZIkSVJBmKyTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgRnd6AyRJkiRJUjVs2RLCpk1bv65bF8LEiSGMGhXCmDFbv0qysk6SJEmSJI2AjRtDuOGGEN71rq1Jupkzt3499dStP+f3kkzWSZIkSZKkYUYi7qKLQli0KISrrtpaWQe+Xnnl1p9ffLEJOwkWmUqSJEmSpGFDQu7WW0M455y+n3f22SHcdtvPEnlSVZmskyRJkiRJw4Y56s47r7Xn8jyeL1WZyTpJkiRJkjRsqJS7+urWnpsPkZWqymSdJEmSJEkaNqz62moCLq0SK1WZyTpJkiRJkjRsWPF1VIvZB57H86UqM1knSZIkSZKGL/EwKoQTTmjtuSee2HpiTyorTwFJkiRJkjRsxowJ4dxzW3suz+P5UpWNHuwLbPq/ZVo2btwYrrnmmjBSRo8eHebOnRtmzpw5Yn9TkiS1bsWKFWHZsmVh8+bNI7bbiEfy+ETVZHwqqVGtVguvvPJKWLNmTVi/fn28XqQHbcZrr70WXn311fhcfnb11VeHMWPGxMfYsWPjY/z48WGXXXYJEydODNtvv707uQ1Uys2fH8L554dw9tm9P4/fH3mklXXqrhh0OOLP0UPR6DVu4EhZunSpyTqpC9FuEAyl4GjLli0xQAJfV61aVQ+KSMxL6k4ESdwQdUIen6h6jE8lEV+++OKL4dlnn40JurVr14af/vSnLe8YYtTebry32267sPPOO4dJkyaFqVOnht133z3suOOO7vR+jB0bwhlnhHDUUSGcd97W1WFZTIJEHkNfqagjUcfzpG6MQYcy/hz0XTANVdogbqxHQkoKjmRPvaT20Ta8/PLLMUBKQRKNJoFPbw0ZQdQNN9xQ/ze9lrQt9GASEPGgR5NeTknFll+nRzpGID5RdRmfStVEHPncc8+FlStXxiRdq8k54ko6iKm8I0alDeG6lTqVG/GcdevWxccTTzwRf7brrruG6dOnx8dOO+005O+tLAgHFi4M4bLL6KTfuuprWnyC8N656tSNMejGYYg/B52so2Fjw9gBixcvDiOB4bYjXcUnqTVUzBEcESQRLA02qU6lHQk/Hs8880z95xMmTKgHRFOmTAmjvLJLhdWJGMGEfrUZn0rVQofwY489Fp588sn6aI1m16LU6Usn8Lhx4+LPaC9SHJlfQ972trfFpBwJvzRclk7n1An90ksv9Xj9F154IT7uu+++WG03e/bsGKcao26L3Z3yJxMmDP3xII10DDoc8afjyyQNGgk55gV46qmnYpDSV/kvQwQIjtL8H3yleu7hhx+Or0Ov5qxZs+pDZPm6YcOGbQIvknePPPJIfOywww4xGOL/I3FnRY0kSVK5EW8+/fTTYfny5XG4ayNiSoanEiOSPBtIdQ0xJbErDxJ8u+22W/13xK0k7eikpkOZ2DRhShcexLl77713TNzZiSSpHSbrJA0YPYr0YlL+36yCjiQaQwLS8FUevQUqvE5K1h1yyCHbBGN5TyaP1atX15OC9HjSk8qD+UMIiPbcc8/49yVJklQexH8kyJi/nGGoOTqAiQH32GOPGIMOZ1UbMStJQB4HH3xwjIsZWfL444/XE3d0PD/wwAOxc3nfffcNc+bMcT5mSS0xWSdpQEMNCJAIlBoNx/DUNIkvj7322qvP4bYESnfffXe4//77wz777BMDI5N2kiRJ3Y8KOmI8RnLkiBGpYCNO7FTcl2JVYs/nn38+PProozFOBXEqE97zs/333z9uq8NjJfXFZJ2kltFLSKDBcNccwQa9mAQeVM+NxDBUhiMQkPFgiCzDDwiA0jAIgqIHH3wwVuztt99+sdqO3lZJkiR1F0ZRMBccVWs54s65c+eGadOmFWYaFLaD4bI8WLCCeJRRKFQEUml3zz33xPdx+OGHx/nzJKkZk3WS+kUyjCQdJfz5fHTMPUf12ute97qOLlefhjzwoOqPpF0KiqjAI7hjPpN58+aFGTNmdGw7JUmS1B5GUNx5551xDuN8JMdBBx0U47qiJOmaGT9+fDjssMPi8FdiaebYA8N3f/SjH8UqOx5W2UlqZLJOUp+oVLv99tt7TJrL8AICiyJWq9FDSVBENV1eBUiAd8stt8SEHkm7TiYXJUnqVn0tIiUNdWfxvffeG0dJJMSdJOm6bRgpw2Pnz58f51wm8UiyjnOJ+ewYHXLEEUfEBSyk4cZxZzveHUzWSeo1QGJeOqrpEoIi5uHohnng6HEl8GFbqaxj7hCQvON7EnrMqydJklpHxToLPTH8UBouqZOVYy1hIQfiN2K8bjV58uTwxje+MQ6N5UHShMTdkiVLwpFHHmlsqmEfTk6ymK8qvu7pjpA0ogHSdddd1yNRR3Dxpje9Kc4LUvREXWOl3THHHBPe8IY31FffYr6QH//4x3GCYnuWJEnq35YtW+JXrpvECKzALg0Hqs8YIpoSdVTTHXrooeHYY4/t6kRd3vl94IEHhkWLFsWKu9RJTmyaEnjSUGOUFElhhmKn9lzFZmVdwZH1pvEu2lBDlXvYKz2ZJLTygIIKtSLPCdIXtpt59Zh8+K677qqvYvvQQw/F3kwq8LopAanuxorFafViSeoWeQzAjR5TZBCnMnetNFQYAXHHHXfUkwnMj3zUUUeVciEGqlNJ2PF+01x2jGohNqWT2fs/DRWOqRtvvLF+f6fuYGVdQaUeFRJ1nlgaKSzKcMMNN9QbcibFJYhg/rduTdTlUsB3yCGH1N8PiTt6mfI5+aThnCSb481knaRuk66beQKBVS2pBJKGAiuk/uQnP6kn6nbdddc4ZLSMibqEUR8Mf6VjPFmxYkXsOOc+UBosKlTz+7uddtrJubu7hMm6gsrLn6l0yk8waTgw5DXvySRAIlFXtgCJmw1W5Dr66KPr1XTr16+PQ3rodZKGy8qVK8PNN9/cI1HnUBdJ3ZhcYJGphEogp5XQYLGIBHNpJYyIYNjrmDFjSr9ziU0POOCAsGDBgnoynM49hsWasNNgpDwCc42C+7rjjz++qxZnqTI/pYJqPIEYNkWFXTrRpKHEcFBW20pYYavsAdJuu+0Wk5H0LoFkOBczE3YaDlRw3nrrrfXkXGrjy1CxKqlaaLeYv5YVOfM4goSdNNCKOqYpSehUZSGJqiUUZsyYETuTU8KOBdGIHZxfTANN1JE/SJ3EU6ZMCQsXLrSqrotUqwXsQjvuuGMYO3Zsj7HmJuw01BV1eYBNzx6T+FYhQCJRR+9Sqh7k3OIcIzkuDRV6xxnOkoLtPfbYwzkSJXU9psiYN29e/d8PP/xwTLpI7XZm5RV1zJF88MEHV7YzixVv6TBPCbu0f6zEVzteeeWVHkOpmbebBfeco7u7lP9uvMuRMCEDniqc1q5dG2666SbnO9KQTeKbV9TRU86cGVUKkEiIc44xyW+qsCNht3Hjxk5vmkpg1apVcRhLStTNnDkzHH744ZU6xySVF4tL0MGXUB1Fuye1gs7R2267rcfxRMVm1a+RVEDlFXasvEznutQKKumIPdMUWiSAmbObKQzUXUzWdUn1T56wY5JI5hazh0WDkY6jvKIun4OmSuhlorcpVdiRqGPYgfOEaLC9mvnwFYa3sPJwFapWJVXH7Nmz6yvCEpvS7rlok/rDSsIkFNIQvenTp/dYAKzqSLCwImxy3333xUp9qS+0wSzSkqb1mTBhQpg/f74rC3cp7xi6xM477xxLolNGnOW97WHRQJGMyqt9mMSXZF2VUWFHL2Yads48D3fffbdJcQ0IiV5uWNO0BcyRyGpvJuoklRHDFmnnQLtHjEEyRuotoUBFXUrqcp9DZ5aJup6YNiPvSCeuYFE0qTcs+MOCZiBvQEUd9zjqTibrusjEiRPj8KmEecYcaqB2kaBjDoM0zJNSe4awGCCFmKjjopYSKk888URYvny5B5nads8998TqVYwfP96KOkmlxnWTDom0aBPDG11wQr159NFH61ViJBIcotc7pqeh6hBUId5+++12JKupF154IS72A+7rqKgjEa7uZbKuyzCMKvWwpF6pDRs2dHqz1EUefPDBsHr16npyytLonpi7Lh92wM2GK8SqHUywniZZ5wZ2wYIF9mpKqsSUEiRd0jxbjz32mJ3K2gbVdHkil4o6huqpOZIuFGukfUQM7+gqNRvRkU9vxDzkqdpZ3ctkXZf2sKSTj4kjqeCQWsECJSTr0sWfJEIa9qmf2XPPPcOcOXPqlYhc/NKQYakvdJ7ki7Ycdthh9bkQJansqKzjJjFhFcs0J5lEoQExVZoTeO+99zah0GIiPO9IZqgj1atSsmzZsvqw8smTJ8dVldX9TNZ1IZIs9EKlBSeeeeaZ+th0qTckm/LS+f322y825mqOm400nIfhjPZiqhUk6tKNKXNB7rXXXu44SZXCYhNMsQFuHrmJlNLwV4bqpSkimOtQrdl1113rC7mkjmQXG1Sqtnz44YfrIzroKHZ6o3IwWdelmN8hv8AxEb49l+oLcxik4ZzMf1j1BSX6wzCevBeTmw0n9VVfnn322bj4T7M2WpKqgptEbhbT/K90dqU5PFVdLDiSJ245RtLCeWq9IzkfDrtixQp3XcWRsKWCOWG6LO7zVA4m67p8qN60adPqQ68eeOCBTm+SCorh0vmEoyShXJWyf1QGpDJyejEZdiA1Q2cJnSYJiTpX35JUVUxqnncKev0UcWhaITi/h1HrSG6yKFxC8tNpWqrtqaee6lGMwcgplYfJui5G0oUGO++5TGPVpRyJ3DQ/yKxZs+IiCmoNNxtpyDlVU2lxDinHqsGvvPJK/H7q1KkOf5VUecz9Om7cuLgfWPkzDX9U9WzcuDFeJ8F9Sz6vodpDkpM4A9z3pQWtVD0kavNinUMOOcRijJIZcLKOcsslS5bEih3wNS/B1MhgTq1U+UMZbFo8QEq4kLMiWxra6fDX9nsx833GCmbOEaIclQJprhDQieJcISPPuEQeB8VCzMGiaIlz11VX3mnMohLMV6eBIb446KCDeuxbp0KqblVdKtQhgWu1avnizwEn6yi3pMIk3bTyNZVgamSRrEtzPjz55JP16g4pXcTTeUovt6u/to9qxBRYrlq1Kj6kfMLsNLSHBSUY/qWRZ1wij4PiYbhjmmOLa6fV6dXDjesTTzxRT+Ayp5YGhwXiZsyYUd+/3P+pWri3S1McIe8YUXniT4fBlmQ5b5Iw6eBw1UolXMDpdUnHict4D0zjkI00lEOiUiA/HrwJkaSe1898DqX85lLVQKIuzatGVV2aWkSDk4/6YASNoz6qZeXKlfWF71gpmIfKx2RdScyePTv2VoG5C1599dVOb5IKgGMhXbypDiNhp4GZOXNmvSqRC6QVrALJ8FT2zjHC1ASSpJ7VdSlBw/XTGLU6iEHTVCwpWaehscsuu8QKO1DF8+KLL7prKyRVq8JFJcrLZF1JEAQx/CpVejARvqrNAGnoqwPyIDMPPlVdebCUKpwlST9DZzIJuxSbrFixwt1TESwskjo3d9ttNzu0hqFYI5+SQ9VAJ/Gzzz4bv6eQgHNL5WSyrkSonEoMhEQjvmHDhrgjdt999/qcMRrcOZYWDqBqMQ3rUDVxA5J6spmnbsqUKZ3eJEkqpNShDOfXqo58pVKr6oYeFf077rhj/J5CjTR/rsqNzzqNnKIjxEXNystkXcnKofNJfFOiRtWUJ2wNkIYGvVdpQl+G8bjQRLXl59gee+zR0W2RpKLHqBMnTozfMxF3mmtJ5cVIHyrrQEJp+vTpnd6k0letpv2tcss7PNLnr3IyWVciZNXzE9ahsNVFxVcqj2alYMujh06elGHuHVWXyTpJal0eo6bFr1RedGiSsAOJOqt/hkeeBDUuLb+XX365vqo2HSB0hKi8TNaVOJHgUNjqYmheKoVnCCzzrWloTJs2rb4/CYpcfauaqApZu3Zt/H7SpEnOwyNJbSTrnn/+efdXyeWJI6vqhg+rgNIxDzrqnaKl3PLqSUd1lJ938CXDvEk8QNbduQuqyQBp+LCi7tSpU+P3DDVnBS5VT36jyZwxkqS+jRs3rj5dCzFqqrpS+dCRmWJROjjp6NTwYP/SMQ/u+1544QV3dYnlq/56XpWfyboSSokEpDJZVbPXhSEHDoEdenkPsfODVFMeLOVtriSp7yqglMxZs2aNu6rECzBt3Lhxm8ovDY+UrGuMT1Q+KRnLfIUOgS0/k3UlNHny5Pr3JuuqZ/PmzeGll16qz2WQVonS0N9swJuNakrBMD3aBkuS1Jp81WwrgMorTRPReF+i4ZHvY+PScifB0wKSfOZOc1R+JutKHgjZu1LtAIm5tDT0dtppp9ijBYOi6qFagIApnWMGS5LUfmeXybryymMjY9Hhx/DyVL1oXFpe+X193paqvEzWldD48ePDmDFj6ie1E+BXS36RtuJneJCcoWoRJG1effXVYfpLKnqwlHeOSJL6TyqkGNWkQnmZrBtZTHuTkqJ0KKYhyCrveWX8WQ0m60reYDMk0ga7WqysGxl5T3G+z1WNlWATKwYkqb0YNS0yQUcXcarKJ8VFJGbHjh3b6c2phLyD3sXPyimN6kijfFR+JutKvOJWYrKuug15WhlYQy/ft/k+V/ml+UIa21pJUmsjQJq1pyoHVvlNIw5IzJKg1fBLSXB4XpVT/rmaBK8Gk3Ulld9A2mBXS0rOMneFq28Nn/wiaUK8WvLP22SdJA38+mmMWj6bNm2qf29CYeQYl5Zfai/5rJ0vuRpM1pWUybrqSokEA6ThZVBUXSlYolrA80yS2mNlXXU6tLxGjhzj0vJXrKZEuB3F1WGyrqRM1lXTT3/609iYwwBpeBkUVVfes+nwHklqjzFqdZJ1aTERDT/j0nLLq5DzDg+Vm8m6FrCaKheeLVu2hG6x44479kjgqBryVUnzY6AbdNuqxXkA6mqw1ZLa1G47x9hurwdSeRCb5kMOu8UOO+xQ/94FJsonj4m6MVnXbfFoYlxabnlbmbeh3YJikpdffrlrz69OGd2xv9wlOKBuv/328NRTT8Uei0WLFnVF6Wk+jr2bkowanPyz3n777btmdz7wwAP1G45uOV7ziqpu2WYNzTUhBRrdNF/Ik08+Ga9lHLcLFiwI06dP7/QmSRqExx9/PNx5553x+8MPPzzstddeXbM/jVHLLY+Juuk6iRdffLEej3Zb51Yel5oQKfd51W2jOqgKXLJkSexgmjp1ajjmmGO6rm3oFPdSC71DJOrAAfb000+HbmCDXU35xbmbGvKHHnqo/n0axlt07N+0jw2KqqNbz7GHH364vv3Lly/v9OZIGqJzGt12Thujllu3XidTEjyPR7spvvO8Krf8WOy2RNfKlSvrw+NXrVoVXnrppU5vUtfork+6AygzzSvpdtlll9Btuu1CqepdqCdNmtSVx2vax920zRqcbv2sJ0+e3PR8k9Sd8vO4287pbopPVJ1YtPE+L++U7QbdnCRVuc+riRMn1r8fPXp0V4xSLAqHwfaDzPXxxx8fK+powCnd7AbdXCqr6jXkRx11VPj+978f52OgEe8GBkXV1K3Dnw899NCYsOOatueee3Z6cyQN0mGHHRbPadqkWbNmddX+7OYKEZU3FsXs2bPDsmXL4hDYbpuXNue9X/l08/QBu+66azj22GPD6tWrw4wZM7r63Bpp3XFX3GFkf+fMmRO6dRLKbpq7TIOTTzjaTYsesN0k6bppoul8/3bjRK8aONpUhsd00/FKkNdtN/SS+m6H9tlnn67cRXnbabKufPJO126c941zi+3utoRXvq+7peNbrcvbym6ZMig3bdq0+FB77M6qwPLOlppWBz0VKbhIcwNoeOT7l8VnVB2pTaWd7baqAUkq0vXTGLV88pjIWHTkGJeWW95W5vf5KjeTdSVlsq6aSNSlpdvTalYaHvn+Tftc1QqY6NnstqoBSeo0Y9RyM1nX+WSdcWn5UC2ZKiZN1lWHybqSMhCqrhQkkUzqtjkNuok9mNWV34gYMElSe4xRy81kXWcYl5a/IMORHdVjsq6kHGJQXXmZ9CuvvNLRbSmzfN86jKda8s/bIT6SNPDr5/jx4919JUP1T5ov2w6tkZPva6dnKXf8STFGN81NroEzWVdS69atq2fhbbCrJV8ee+3atR3dljJbs2ZN032u8stvLlNbK0lqL1lHQscFmsqHe4+ddtopfv/yyy87XcQIyWP+nXfeeaT+rEaQBRnVY7KuhJhD6aWXXqonEVwNtlomTZrUNKGkocOiAmnfcqNhZUC1TJ48uf79iy++2NFtkaRuQjUICZyUUOi2FTfVfixqp9bISHEp930pWapyyZOwq1ev7ui2aGSYrCuh/OSdMmVKR7dFI89k3fBj6GNaYIL97c1G9YKlNMkv7a0rwkpSa/IOjl133dXdVlLGoiOLmDQNgzUuLa+8zXzhhRc6ui0aGSbrSh4ImayrHoY9p1Wg6GVzkYnhTYjvsssuw/AXVGQkZ1PbSoDs3JCS1Jr8BtMYtbxM1nVuCGy+71Uu+Yg57vftLC4/k3UlZCCk1POyefNmh+kNg2effbb+vZUB1ZTfZNq7KUmtsUO5ekmF559/3qTCMGMfJybrymvUqFH1+JNRPnYWl5/JuhLOBZJuHJmE0lUqq2n69On171euXNnRbSkberHSPiUQnTZtWqc3SR2QJ2k9xySpf3Qgpsr0CRMmuABayZMKKT6iAt05lIfXM888U6/832233Yb5r6mTHApbLSbrSthYp5LYmTNnOpdWRe2+++71z55EgmXSQ4cbjbRcOoGoC7hUEz2babg5lZYs7CNJ6h1tZYpHpk6d6q4qOTuORwaLCqZFW4hNdtxxxxH6y+p0si4laVVeJutKZsWKFfXv99hjj45uizqHC3VqzLmAp9WBNXj5hTEPRFW9qgE6RMC8kFbXSVLfnnzyyfr3xqjV6DhOvEYOn3zfGpeWH/d3zE+eOkDSgncqJ5N1JcLY9TRnwfjx452zoOLyC/bjjz/e0W0pC5Iy6WaDykWDomrLbzafeuqpjm6LJBUZN5TPPfdc/J4bTSvryo/PefLkyfH7devWxYeGFpWqeaGGcWn5cf+x5557Nv38VT4m60pcVZeGQaqa9tprr1j9gyeeeCLOFaPBefrpp+s9WDNmzKgPg1Q1MdwkzQtKRwkdJpKk5jFqGgLLjaYxavU6tR577LGObktZp2ZJK8GysMROO+3U6U3SCN3jJXYWl5vJuhJV/Cxfvrz+75RxV7WHwqbjgESdPS+Dlweas2fPHoJXVJl6Nx999NFOb5IkFX4IbH6jqXJ73eteV5/bl2PA+V2HVh53GJdWa7VlHilh63RH5WWyrkTzaKXlm1kFKJ3Aqrb8wk0y14UmBo6ey7TS8s4779xjgldV+xxLFSIEzVawSlJPXDvTaqD5TabKb4cddujRcWwV0NBhpAcjPtJ+dh7Iask7PfKCHZWLyboSIAHz4IMP1v89Z86cjm6PioOSeB5grpB0UVf7li5dWv9+7733dgiPIobBphsRKgasrpOknh566KH698ao1dPYccxoIA3NaI+0L/MKRlVD/pkz3ZFTsZSTybqSVNWlSVuZyHXatGmd3iQVyIEHHtgj4WSQNLCqAFZcShMmz5o1awg/IXW7/fbbr/79ww8/bHWdJP0fhmil62feuaHq2GWXXeqjEdavX99jSLQGXlVHvAGq+x0CW83pjtLnzr1d3imi8hhwso4SdhJDafgPXy1rH3mUlN933331fx9wwAFW/KgHhkWnIOnll1+OvS9qr3L1/vvv75H8tPdSOYZFp+Enr776anjggQfcQR1gXCKPg+JZtmxZj46NtPCVqmXu3Lk9jonXXnuto9vT7RhRlabdoMJqwoQJnd4kdQCVyumehErLDRs2+DmULP4c8BXzsMMOC4sWLaqvhshXfqaRb6zTXHVTp06NiRkpR4Nx0EEH9QiSnOC3vcrVF198MX7PKltOjK1mSOKmm9BHHnmkvjqbRo5xiTwOioVVsp977rn4/fjx461KrzA6jXfffff4PcP1nDJi4LjvSwuekaihUEPVxGifvLoun7JH5Yg/7d7qYgx9TSXQ3CQeeuihVtWpqSlTpoTp06fXS+fvvfde91QL2Fd33313j55hqwLUDInc/fffv16Nedddd7mgi6TKournzjvvrP+bhILXz2rLO44pNnCOrYFhRFWa0mafffaJw8tVXVQsjx49On7PEPPUQaJyMFnXpRpvBjlRGYol9WbevHn1xpyhsGkOGfXunnvuiQk7kOycMWOGu0u92nfffetDUZin6fHHH3dvSaokqvjTyA86DK1KF8PC0nHACA87tdq3YsWK+mJxzFmWz5mrauI4yBPhnFdpiLS6n8m6LkVFXRqax82hjbX6wxCUQw45pP5verwdDts7giGCIuywww7h9a9/vZWr6hPDUThO8t7vl156yb0mqVKIT5kOAFTTveENb/D6qejggw+uDxVbuXJleOqpp9wzAxztQSc88am099571+cnp5PE4bDlYbKuS+cAySe85+bQCe/VCiahTasFM/zg9ttvd6heEyzEkQ/fISBiXgipP5xfqXKAns1bbrnFpLikymDhgDvuuKPHfJ5MEyCBRB3T9uQjGBwO2z9GUpGoYxErMNIjLWwlMT85c6SlfMDy5cvDCy+84I4pAZN1XYZs+W233dZjDpCUfJFaaczp4U49cfRq5iu1aevQjB//+Mf1BAsB0Z577umuUcu4EUnTEqxfvz4mftOUBZJU9oQC7R4mTZoUVyuUcjNnzqwnmoi1uK9Jc7CpORaUSMNfieGdp1yN6BShcyThvDIR3v1M1nVZb+Wtt95a71VhVSVXAFK7mIj2yCOP7DHJbxruWXXcaFBtmIYucuFz+I7axdyQCxYsqM8RSYCdhoRJUlnRzjEnbj781UUl1AwjFtJwWCqASPLaqdX7iCoqEPMRVY72UDN0jjBHKEjUMbqD/IG6l8m6LkvUrVmzpj7/2OGHH+4cIBqQ3Xbbrcf8dQxZSXMgVhVBInOMUW2Yei6POuoo5wPRgJDoPeKII+r/5thKN7GSVDZcO2nnEmJUFhSQmiFRR6dWSuayIBPVY9p2Whbu/1Iik4WsHP6qvkZQzZ8/v75CMIudObqju5ms6wKUhlPKmlbvZDw6FzhWf5EGiuXemcMuJYNvuumm2KhXFcOBU/UTFzuqD51nR4PBCsJ59TNJ8SeffNKdKqlU1q1bF37yk5/U/027Z0JB/aECKF+Uieqx5557zh33fxhJlU/LQkd7vuqn1AxVlxQbpPnrWMTloYcecmd1KZN1BUdPCom6VO3DiXf00UeHXXbZpdObpi5HQoo5L9Kch0yGf+ONN1auwo5zjFWTGA6cEDwSFEmDxU3r7Nmz6/9mmLXDziWVBdNG0NlHDJHmI3OKFrWKTmOqxVI8xrC9VatWVX4HkqDjvMqnZaETmdhd6g95AqqbE+5zWHRC3cdkXRc01s8880z8nlJxMuVTp07t9GapJFKVZlruOyXsqtKzSdXqvffe2yNRxzwqs2bN6uh2qTwIrDmm9t577/rPqEBxDhFJ3Y6pWa6//vr6JObcIDrPq9pFtRiLeYFr480331zphF1K1KWpjxgy7LQsahcdJ3Pnzu1RuZrf76g7mKwrqDQ3QVodKSXqXPlVQ41J8KnWTEngFCgxJLTMk/2mVV/zniaSKgwPloajijUlgTmv0rCWMp9jksqLKvwbbrihvugZibpjjjmmvrCO1Ko09QgL5+VTs6Tpf6pk06ZN8bxK09Iw5dGxxx7rtCwakP322y/sv//+PSrseBh7dg+TdV1S/USizmF5Gu6EHXNsgUacijMmJS1jBRDDCn70ox/VKwgJFA877DATdRo2HGMMr84r7CSpGxEXUIWfhr4y99jChQvrq3tK7aIogYnxU8KOYoUqdBzn1q5dG2NTvuaJOhdq0WBiT6rr8rkOqa6jyq4q51W3M1lXUGlOAr4uWrTIRJ1GbEgsvTAJq1cyxCXNmdHtuDDxnpYsWRJX2MqDIYe+aqQq7FiJOW/jJakbpJs7KoNTRx4jPqioYwV1aSjiUIbvJXQcszhTGTuOc8xle91114UNGzbUFwk47rjjnKNcQ4J7O0YPJY8++mh9hIeKzWRdwZFIsEdFI4XEAb0vRxxxROzlBHNm/PCHP4wrCXVzLwwBEMNeCfpSNQDnFslw54HUSJ5jc+bMcTVvSV2nMQagGp+RHw591VAh9mRIbD50j1XUGRr6yiuvlG5HU0F4//33x8UEU0Jy8uTJ4Y1vfGPYeeedO715KhGm+cnnFE1TbanYnFii4Ky6UCfsueeeYcKECXEifCrQUjDBYicM5eum1YhTNd19993XoxeJ98h78SZDnWDbLqnbpE48kEw58MADbcs0bEP36FBNVXXM4faDH/wgHHzwwXE6iTJcQxnuygrx69atq/9sr732irEpVYbScKy+PH78+JgcZr7Rbi7CqAqTdZKaomfvTW96U1i2bFmcMwQES1TZkegiSCehV1RcgFauXBknUs2H8TKnDoFQWnlMkiS1N+ojX2VQGg577LFHjDNvueWWODqCpN3dd98dnn766VghRNKhG9EBzrxhPFKyJI1sofK+DIlIFRejiajc5H4uLRCk4jJZJ6n3BmL06Di/FoktejfTPG9PPfVUnF+D3s199923UAETgc/zzz8fHnjggbhaXY4kI3M2cKMhSZIGV2EnDadJkyaFN7/5zXF0xOOPPx5/tmrVqlhlx7A+5uLqlvkSiU8ZocJIlRRPgwpCko+8V2kkjBs3zna8S5isk9SvXXfdNQZLjz32WOwJTKXTTFDKg3lrSNyxYnGnegQZ4spwV7Zx/fr121QJ0mPp3HSSJEndg2TcYYcdFheeuPPOO+tVdsylTMzHkOzZs2cXeugoncgk6ZgHOiFeZtt5mACX1IzJOkktIQiiPJ/5Dh5++OE4NDZNhstwUx4MV2DYwu677x4TZMOduCNBRwBETyWPxtXCdtppp5ikI5nosAJJkqTuRIcwHcdMz0KSjuGkxIFU3RGX0mk8a9asWDVUBGwfQ3bp1G4c6UEnOCNXrKaT1BeTdZLa7uFkrhqGHxAs8di4cWP8HWX9aR4O5oYjaTdt2rS4IAWJs8EmzFjFlYl4mTvvueeei0Mhmq1mRAUdQRvDd+2tlCRJKkcMynQmdB6TtGOlWGzatClOf0L8SexHDEgs2ImOWmJhhuwy2oPtyjHklU7kTo5EkdQ9TNZJGhCScQcccECcL+TZZ5+NPYdUuSUEKAQqPGJjM3p0TNoRqIwdO7b+4HWo2ktJN76SjCMBmB4MeWDoQL5QxDaN2ejRcRUtAjT+hiRJksqHuZIPP/zwOG8ySTtGV4ApWqhm48H8xHQaM7qC5Bhx4nDgbxKjplEm+equCR3WDHdl7mSTdJJaZbJO0qBQuUYvJg8SayTuCFZI3OXDUqmKe+GFF+KjL8yHt2TJkpb+NkMdCMJ4MKSgyPOVSJIkaejQObtgwYLYqctIDyraUjUb8SSVdzyIVRlymh50Hu+8884DSpwR665duzYm6HjQwdxYQQdem9iY+fSIUU3SSWqXyTpJQ4ZKOeYL4ZGScymY4ZGGyw4EQQ6BVQq0pkyZEoM0gx9JkqTqovOWKVoY8UGV3YoVK+J0KanTmFEbzBuXzx1HAi8f5cFXqu+YBw9pPrx8pAdJufT73hCjkqRjjmdeU5IGymSdpGFBwMPwAx4JQQ5zeTQGPmkSXr4SPKUAJ38whMDKOUlSK1KlC9eZa665ZkR22mA6pCQNHjEkC53xIFHH3MaM9iBx98orr/R4LjEnP2v8ecL/z8IVrcS7VM6lkR4m6KRi2ThCcUCKAZpV2nYsWWcwpDJh2jTOL74y5QRTn40axfxsW79qcOi55NEMARWNHHOMvP71r3dXSyXSiYTJUAZL6j7MI9WpJNpwzY0lqXV08OadxlwT8uGr69evjz9juGyrr0cijio+htGmkR4TJkxwlIdUQKOza/FIxgF5/DFYg44mDIZUFpzDt94awqc/HQL3lCTsSNCdcEII554bwvz5DPPs9FZKUvfoVKA01MGSug9TJKRjYCQrXTjmGY4nqVjoLGahCR6NFXQk7bhG8f2tt94ah7qy8izz4eVDZJ16Reoec+fODUuXLo1TM42EFOcOZTsx6GSdwZDKgHProotCOOecnj8nYXfllVsf558fwhlnmLCTpKIGSsMVLKn7cIPNscBN9uLFizu9OZIKioo5Vpflkf5Nso6vU6dO7fTmSRqgmTNnxsdIYQQJcUdvo8g6kqwzGFK3IyFHRV1joq7R2WeHcPTRIRx7rENiJamIgdJwBUuSJEnSSHIWLlUe0xqdd15ru4HnOQ2SJEmSJEkaLibrVHlU1l19dWu74aqrtlbiSZIkSZIkDQeTdao8Vn1tNQGXVomVJEmSJEkaDibrVHkTJ7Y+Bx3P4/mSJEmSJEnDwWSdKo8E3AkntLYbTjzRxSUkSZIkSdLwMVmnymPBwHPPbW038DwXGJQkSZIkScPFZJ0qj8q6+fNDOP/8vncFvz/ySCvrJEmSJEnS8DFZJ4UQxo4N4YwzQliyJIS3v/1nCTm+nnRSCNddt/X3PE+SJEmSJGm4jB62V5a6DIm4hQtDuOyyEF57beuqr2nxCYa+troIhSRJkiRJ0kCZrJMyJORS9dyECe4aSZIkSZI0sqwVkiRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKgiTdZIkSZIkSVJBmKyTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQYzu9Aaoi23ZEsKmTVu/rlsXwsSJIYwaFcKYMVu/SpIkSZIkqS1mVDQwGzeGcMMNIbzrXVuTdDNnbv166qlbf87vJUmSJEmS1BaTdWofibiLLgph0aIQrrpqa2Ud+HrllVt/fvHFJuwkSZIkSZLaZLJO7SEhd+utIZxzTt/PO/vsEG677WeJPEmSJEmSJPXLZJ3awxx1553X2nN5Hs+XJEmSJElSS0zWqT1Uyl19dWvPzYfISpIkSZIkqV8m69QeVn1tNQGXVomVJEmSJElSS0zWqT2s+DqqxcOG5/F8SZIkSZIktcRkndpDAu6EE1p77okntp7YkyRJkiRJksk6tWnMmBDOPbe15/I8ni9JkiRJkqSWWPak9lApN39+COef3/fz+P2RR1pZJ0mSJEmS1AaTdWrf2LEhnHFGCEuWhPD2t/8sIcfXk04K4brrtv6e50mSJEmSJKllo1t/qpQhEbdwYQiXXRbCa69tXfU1LT7B0FfnqpMkSZIkSWqbyToNHAm5VD03YYJ7UpIkSZIkaZAcBitJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKggXmJAkSVKpbNq0KX7duHFjuOaaazq9OaXAvpQklYfXyKG/Rqb4oxDJOoOh4WFAJEnlZ5A0PPt0qIMldZ9arVb/3phqaI0ebV+/JJWlHfcaOXzxx2AN+mprMDS8DIgkqXwMkrorWFL32W677erHwNixYzu9OaVqu+bOndvpzZAkDQLt+NKlS8PmzZvdj0MkJT2JPwqTrDMYGj4GRJJUTgZJ3RUsqfuMGTMmHgsk6hYvXtzpzZEkqTBmzpwZHxo6TLlB3EH8UZhkncGQJEntMUjqrmBJkiRJGkmuBitJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKgiTdZIkSZIkSVJBmKyTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZisk1QImzZtCmvWrIlfJUmSJEmqqtGd3gBJ1Xb99deHCy+8MFx++eXhtddeC9tvv304+eSTw1lnnRUWLlzY6c2TJEmSJGlEWVknqWMuueSSsGjRorB06dLwuc99LlxxxRXxK/8+/vjjw6WXXuqnI0mSJEmqFCvrJHWsou70008PH/zgB8MFF1wQRo36Wd/BmWeeGT784Q+H0047LcybN88KO0mSJElSZVhZJ6kjGPo6d+7cbRJ14N/p93yVJEmSJKkqTNZJGnEbNmyIc9S9//3v3yZRl/Bzfv+d73wnPl+SJEmSpCowWSdpxK1bty4uJjFnzpw+n7fPPvvE5/F8SZIkSZKqwGSdpBE3ceLEuOrrI4880ufzli9fHp/H8yVJkiRJqgKTdZJG3Lhx48LJJ58cvvKVr4QtW7Y0fQ4/5/ennHJKfL4kSZIkSVVgsk5SR7Da69KlS8NZZ521TcKOf6ff81WSJEmSpKoY3ekNkFRNxx13XPjSl74UTjvttHDttdfGxSSYo46hr1TUkajj9wsXLuz0pkqSJEmSNGJM1knqmA984ANh3rx54cILLwwf+9jH4mISzFHH0Ncvf/nLJuokSZIkSZVjsk5SR1E5x+OKK64Iq1evDpMnTw7vfOc7/VQkSZIkSZXknHWSCmHMmDFh0qRJ8askSZIkSVVlsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpIIwWSdJkiRJkiQVhMk6SZIkSZIkqSBM1kmSJEmSJEkFYbJOkiRJkiRJKgiTdZIkSZIkSVJBmKyTJEmSJEmSCsJknSRJkiRJklQQJuskSZIkSZKkgjBZJ0mSJEmSJBWEyTpJkiRJkiSpIEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgRnd6AyRJkqThsHHjxnDNNde4cyW13GbYdkgaaNsxlEzWSZIkqVRGjx49rAG0pPKz7ZA0mPhjsEzWSZIkqVTmzp0bli5dGjZv3tzpTZHUpQm6sWPHdnRbJHVfoo74Y6hsV6vVaoN5AYYWpEbNBk3SQBkcSRoKeUyyePFid6okSZK6zqAr6xxmIGmoOexAUpGGIUiSJEkjadCRrMMMJA0FK+skFXUYgiRJkjSSBj0MVpIkSZIkSdLQGDVEryNJkiRJkiRpkEzWSZIkSZIkSQVhsk6SJEmSJEkqCJN1kiRJkiRJUkGYrJMkSZIkSZIKwmSdJEmSJEmSVBAm6yRJkiRJkqSCMFknSZIkSZIkFYTJOkmSJEmSJKkgTNZJkiRJkiRJBWGyTpIkSZIkSSoIk3WSJEmSJElSQZiskyRJkiRJkgrCZJ0kSZIkSZJUECbrJEmSJEmSpFAM/z9GO3dDtCBAEAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(16, 5))\n", + "\n", + "# 1. Plot original frame (using 0-100% coordinate system)\n", + "container.plot_frame(0, ax=axes[0])\n", + "axes[0].set_title(\"Original Frame (0-100%, Top-Left Origin, AwayHome)\")\n", + "\n", + "# 2. Plot transformed frame (using meters coordinate system)\n", + "normalized.plot_frame(0, ax=axes[1])\n", + "axes[1].set_title(\"Transformed Frame (Meters, Center Origin, StaticHomeAway)\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Common Data Format (CDF) Schema\n", + "\n", + "TDC uses the **Common Data Format (CDF)** standard for naming columns. This standard defines a hierarchical path structure (e.g., `teams/{team_id}/players/{player_id}/x`) that allows for standardized selection logic regardless of the data provider." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['cdf/skeletal/0.2.3', 'cdf/tracking/0.2.3']" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tdc.list_schemas()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Selecting Columns\n", + "\n", + "TDC provides multiple ways to select columns, from metadata-aware selectors to simple string-based wildcards." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ball columns (using selector):\n", + "['ball/x', 'ball/y', 'ball/z']\n", + "\n", + "Ball columns (using wildcard 'ball/**'):\n", + "['ball/x', 'ball/y', 'ball/z']\n", + "\n", + "Player X columns:\n", + "['teams/home/players/p1/x', 'teams/away/players/p2/x']\n" + ] + } + ], + "source": [ + "# 1. Select using metadata selectors (e.g., all ball columns)\n", + "ball_data = container.select(container.selector.ball())\n", + "print(\"Ball columns (using selector):\")\n", + "print(ball_data.to_polars().columns)\n", + "\n", + "# 2. Select using recursive string wildcards\n", + "ball_data_wildcard = container.select(\"ball/**\")\n", + "print(\"\\nBall columns (using wildcard 'ball/**'):\")\n", + "print(ball_data_wildcard.to_polars().columns)\n", + "\n", + "# 3. Select all x-coordinates for all players\n", + "player_x = container.select(\"teams/*/players/*/x\")\n", + "print(\"\\nPlayer X columns:\")\n", + "print(player_x.to_polars().columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Schema Validation\n", + "\n", + "You can validate whether the data follows the CDF standard." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Schema is valid CDF!\n" + ] + } + ], + "source": [ + "# Validate CDF compliance\n", + "try:\n", + " container.schema().validate()\n", + " print(\"Schema is valid CDF!\")\n", + "except tdc.exceptions.SchemaError as e:\n", + " print(f\"Schema validation failed: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Schema validation failed: schema error: Invalid value for column 'period': wrong_half\n" + ] + } + ], + "source": [ + "try: \n", + " base_time = datetime(2023, 5, 27, 13, 30, 0, tzinfo=timezone.utc)\n", + " data = {\n", + " \"frame_id\": [1000, 1001, 1002],\n", + " \"timestamp\": [\n", + " base_time,\n", + " base_time + timedelta(milliseconds=40),\n", + " base_time + timedelta(milliseconds=80),\n", + " ],\n", + " \"period\": [\"first_half\", \"first_half\", \"wrong_half\"], # <-- invalid value\n", + " \"match/id\": [\"match_123\", \"match_123\", \"match_123\"],\n", + " \"ball/x\": [50.0, 51.0, 52.0],\n", + " \"ball/y\": [12.0, 13.0, 14.0],\n", + " \"ball/z\": [0.0, 0.0, 0.0],\n", + " \"teams/home/id\": [\"team_A\", \"team_A\", \"team_A\"],\n", + " \"teams/home/players/p1/x\": [23.5, 23.6, 23.7],\n", + " \"teams/home/players/p1/y\": [24.2, 24.3, 24.4],\n", + " \"teams/away/id\": [\"team_B\", \"team_B\", \"team_B\"],\n", + " \"teams/away/players/p2/x\": [33.1, 33.2, 33.3],\n", + " \"teams/away/players/p2/y\": [34.9, 35.0, 35.1],\n", + " }\n", + " tdc.TrackingDataContainer(data, validate=True)\n", + " print(\"Schema is valid CDF!\")\n", + "except tdc.exceptions.SchemaError as e:\n", + " print(f\"Schema validation failed: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that you can still add columns that are not defined in the CDF." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Columns with custom metric: custom_metric\n" + ] + } + ], + "source": [ + "# Add a non-CDF column\n", + "container_with_custom = container.add_column(\"custom_metric\", [1.1, 2.2, 3.3])\n", + "print(f\"\\nColumns with custom metric: {container_with_custom.to_polars().columns[-1]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inspecting the Schema\n", + "\n", + "You can inspect the rich metadata associated with each column through the container's schema." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Column: frame_id | Entity: / | Attribute: frame_id\n", + "Column: timestamp | Entity: / | Attribute: timestamp\n", + "Column: period | Entity: / | Attribute: period\n", + "Column: match/id | Entity: / | Attribute: match\n", + "Column: ball/x | Entity: Ball | Attribute: x\n", + "Column: ball/y | Entity: Ball | Attribute: y\n", + "Column: ball/z | Entity: Ball | Attribute: z\n", + "Column: teams/home/id | Entity: Team | Attribute: id\n", + "Column: teams/home/players/p1/x | Entity: Player | Attribute: x\n", + "Column: teams/home/players/p1/y | Entity: Player | Attribute: y\n" + ] + } + ], + "source": [ + "schema = container.schema()\n", + "for field in list(schema)[:10]: # Show first 10 columns\n", + " if field.entity:\n", + " print(\n", + " f\"Column: {field.name():<30} | Entity: {type(field.entity).__name__:<15} | Attribute: {field.attribute}\"\n", + " )\n", + " else:\n", + " print(f\"Column: {field.name():<30} | Entity: {'/':<15} | Attribute: {field.attribute}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/02-parsing.ipynb b/examples/02-parsing.ipynb new file mode 100644 index 0000000..7230d85 --- /dev/null +++ b/examples/02-parsing.ipynb @@ -0,0 +1,355 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Efficient Parsing with TrackingDataBuilder\n", + "\n", + "Parsing raw tracking data (from XML, JSON, or CSV) is a computationally intensive task. While full-featured parsing is out of scope for the `tracking-data-container` project (this should be implemented by libraries such as [kloppy](https://github.com/py-sport/kloppy)), TDC provides optimized primitives for writing efficient parsers.\n", + "\n", + "By moving the parsing logic to Rust and providing the `TrackingDataBuilder` primitive, TDC achieves ingestion speeds that are often an order of magnitude faster than traditional Python-based approaches. Furthermore, the zero-copy conversion to Polars ensures that even large-scale tracking data can be moved into high-performance analysis environments with minimal latency." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## High-Performance Ingestion in Python\n", + "\n", + "The `TrackingDataBuilder` allows you to ingest data directly into Arrow memory. It handles internal column mapping and memory growth efficiently." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ingested 10000 frames in 0.0794 seconds.\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "import tracking_data_container as tdc\n", + "\n", + "builder = tdc.TrackingDataBuilder()\n", + "\n", + "\n", + "def custom_parser_simulator(builder, num_frames=10000):\n", + " for f_id in range(num_frames):\n", + " # Use .insert() for all types\n", + " builder.insert(f_id, \"ball/x\", 50.0)\n", + " builder.insert(f_id, \"ball/y\", 20.0)\n", + " builder.insert(f_id, \"ball_status\", True)\n", + "\n", + " for p_id in range(22):\n", + " builder.insert(f_id, f\"teams/home/players/p{p_id}/x\", 10.5)\n", + "\n", + "\n", + "start = time.perf_counter()\n", + "custom_parser_simulator(builder)\n", + "container = builder.build()\n", + "end = time.perf_counter()\n", + "\n", + "print(f\"Ingested {container.num_rows()} frames in {end - start:.4f} seconds.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing Parsers in Rust\n", + "\n", + "For the ultimate performance, parsers can be implemented in **Rust**. \n", + "Here is a more extensive example of how a Rust parser is structured using `Rayon` for parallelism:" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "/* \n", + "// Rust High-Performance Parser Pattern\n", + "use tdc_core::datacontainer::builder::TrackingDataBuilder;\n", + "use arrow::datatypes::DataType;\n", + "use rayon::prelude::*;\n", + "\n", + "pub fn parse_tracking_parallel(chunks: Vec) -> TrackingDataContainer {\n", + " chunks.into_par_iter().map(|chunk| {\n", + " let mut builder = TrackingDataBuilder::new();\n", + " \n", + " // Pre-resolve column IDs to avoid string hashing in the loop\n", + " let x_id = builder.get_or_create_column_id(\"ball/x\", DataType::Float64);\n", + " let y_id = builder.get_or_create_column_id(\"ball/y\", DataType::Float64);\n", + "\n", + " for raw_frame in chunk.frames {\n", + " let row = builder.get_row_index(raw_frame.id);\n", + " builder.insert_float_at_idx(row, x_id, raw_frame.x);\n", + " builder.insert_float_at_idx(row, y_id, raw_frame.y);\n", + " }\n", + " builder\n", + " }).reduce(TrackingDataBuilder::new, |mut a, b| {\n", + " // Highly optimized merge operation\n", + " a.merge(b);\n", + " a\n", + " }).build().unwrap()\n", + "}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmark: Loading Data\n", + "\n", + "We compare the time taken to load a full Sportec (DFL) tracking XML file using a specialized Rust-based parser that implements the principles above." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "POS_FILE = \"data/DFL_04_03_positions_raw_observed_DFL-COM-000001_DFL-MAT-J03WMX.xml\"\n", + "META_FILE = \"data/DFL_02_01_matchinformation_DFL-COM-000001_DFL-MAT-J03WMX.xml\"\n", + "\n", + "# Ensure data is present\n", + "if not Path(POS_FILE).exists():\n", + " print(\"Downloading data for benchmark...\")\n", + " from huggingface_hub import hf_hub_download\n", + "\n", + " hf_hub_download(\n", + " repo_id=\"pysport/idsse-data\",\n", + " filename=\"DFL_02_01_matchinformation_DFL-COM-000001_DFL-MAT-J03WMX.xml\",\n", + " repo_type=\"dataset\",\n", + " local_dir=\"data\",\n", + " )\n", + " hf_hub_download(\n", + " repo_id=\"pysport/idsse-data\",\n", + " filename=\"DFL_04_03_positions_raw_observed_DFL-COM-000001_DFL-MAT-J03WMX.xml\",\n", + " repo_type=\"dataset\",\n", + " local_dir=\"data\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- Loading benchmark ---\n", + "TDC (Rust): 1.67s\n", + "Fast-Forward: 2.06s\n", + "Kloppy: 22.91s\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "import tracking_data_container as tdc\n", + "from kloppy import sportec\n", + "import fastforward as fff\n", + "\n", + "\n", + "print(\"--- Loading benchmark ---\")\n", + "start = time.perf_counter()\n", + "tdc_container = tdc.load_sportec(POS_FILE, META_FILE)\n", + "tdc_load_time = time.perf_counter() - start\n", + "print(f\"TDC (Rust): {tdc_load_time:.2f}s\")\n", + "\n", + "start = time.perf_counter()\n", + "fff_dataset = fff.sportec.load_tracking(POS_FILE, META_FILE)\n", + "fff_load_time = time.perf_counter() - start\n", + "print(f\"Fast-Forward: {fff_load_time:.2f}s\")\n", + "\n", + "start = time.perf_counter()\n", + "kloppy_dataset = sportec.load_tracking(\n", + " raw_data=POS_FILE, meta_data=META_FILE, coordinates=\"sportec\"\n", + ")\n", + "kloppy_load_time = time.perf_counter() - start\n", + "print(f\"Kloppy: {kloppy_load_time:.2f}s\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9sAAAGGCAYAAABrBpLcAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAU8JJREFUeJzt3Qd4VHX2//FvCiUCCV1AkCYCKsWCiIANV2ysvSCLiKhrQUVxEWzg2rF37IiLCnZRFwsWUFEBKdKUXkWKdKnJ/J/Pd393/pNkEpJwyeSE9+t55kky5c6de89M5tzz/Z6bFIlEIg4AAAAAAIQmObxFAQAAAAAAkm0AAAAAAPYAKtsAAAAAAISMZBsAAAAAgJCRbAMAAAAAEDKSbQAAAAAAQkayDQAAAABAyEi2AQAAAAAIGck2AAAAAAAhI9kGgL3E119/7ZKSktzbb7/tLBg0aJBf39WrVyd6VQBv4cKFPiaHDh2aK05Lu02bNrnLLrvM1apVy7/ePn36JHqVAKDEI9kGgBDoy7e+gMZeatas6Y4//nj33//+l21cgowaNcode+yxfv/ss88+rlGjRu788893o0ePTtg6vf766+6xxx4r9ueNRCKuY8eOrkaNGm7NmjW5br/yyitdmTJl3JQpU7Ilm7rcfffdcZfZrVs3f3vFihWzXX/ccce5Qw45pNQfJCpOl1xySbbPnPT0dNeqVSv38MMPu23btoX6XPfee6//nLvqqqvca6+95rp37x7q8gGgNCLZBoAQ/fvf//ZfRIcNG+b69evnVq1a5U499VT30UcfsZ1LgIceesj9/e9/94nJgAED3KOPPurOOeccN2fOHPfmm2/udcm2tsNzzz3n1q9f72666aZst40fP949//zz7vrrr3etW7fOdlv58uXdG2+8kWt5mzdvdh988IG/fW9x2223uS1btiTs+cuVK+c/c3RRQly1alW/L3v06BHq83z55ZfuqKOOcgMHDnT/+Mc/3OGHHx7q8gGgNEpN9AoAQGlyyimnuCOOOCL6d69evdy+++7rE5PTTz89oetmhRK2ChUqhL7cnTt3urvuusv97W9/c5999lmu21euXOlKy2stjIMOOsj961//8omaKqWq+u/YscNdccUVrl69eu7OO+/M9RgdQHr33Xfd1KlTfSU1oER7+/bt7uSTT/bJ2d4gNTXVXxL5/Ep+A1dffbVr27atGzFihHvkkUdcnTp1irzsrKwsvz918ETvD8VKmO9HLb9s2bKhLRMAShoq2wCwB1WuXNmlpaXl+jKuL5mqZB588MH+i6wS8n/+859u7dq12e7XoEEDn6R/++237sgjj/T31bBnVc5zWrdunbvhhhv8Y1Ttqlu3rrv44otzzXnWc99zzz3+di2vU6dObu7cuXGH/E6bNs0nXxpufcABB0SH8n7zzTf+C71eW9OmTd0XX3yR7fGLFi3yX/p1m+5TrVo1d9555/lhyPGG32t5ur+Gdmu98qLlaj20bn/88Ye/7q+//nKzZ8/e5dxu3b5hwwbXvn37uLfruXMOXVbCcsstt/h5qkqKVRVfsmRJrse+9dZbvtKn11q9enWf/CxbtizbfZTIamj1vHnzfLJaqVIlP+Ra2/rjjz/2ry0YDqx9GNBwYFUT9bq1X5UAa9REvGHC//nPf3ycaH9VqVLFHXPMMXEPLOR0++23u8aNG/sYVHKlYcjTp093Tz31VNyDAe3atXMNGzb0FflYw4cP94m2qqvFbf78+T7G9Nx6/arCarvG0mu74447/L7KyMjwr03D6L/66qu47yftM91P72NVinVdTvHmbOvv3r17u/fff9/Hqvab3uvxpioo1nSATu9F7QONNNideeDJyck+piR4vxU0hoL11n7U+uq+Wmddv2DBAr89gxgNlq0kPDioqNeggy+vvvpqtuUG0w80skSfe3qdWvbMmTOjr/W3337z7xttb01rUExqmoPeb2eccYYfIq/3oWKzKPs0dh00YiNYhzZt2rgJEybk2o76TNH0Eq1L8Dl36623ZruP3uOXXnqpf+3BPn755ZeLtN8AlE5UtgEgRBqOq6ROXxL1JfTJJ5/0jYViK0+ipEaJZs+ePd11113nv8gqsZk8ebL77rvv/DzZgBLhc88913+h1Rd+fZlTEqAvl/pyJ3oOfcGcNWuW//J32GGH+fX48MMP3dKlS30CGLj//vv9F3INNdX6Dh482Cd9P/74Y7Z1VOKvRP/CCy/0Scyzzz7rf9cXcTVH0nzeiy66yD344IN+/fSlWAmk6Mvr999/7++v5FlfdPV4JQH6gq1kKJYSbX2p1ZdmVXvjUZJ6wgkn+GTq888/j76mn376yc+NVzKhL+55UTKtL82as33ttdcWKCHUQQl9Qb/55pv9/lSicOKJJ/o5zFqWBPtRX9rvu+8+fxDg8ccf9/tR+1OJWmw1r3Pnzq5Dhw7+S7+2gxII7QftJw1rl2C+sw6MKMHXwRZVmps3b+5++eUXfz8lJ0rmAqpA6/UfffTRfjqDKobap6own3TSSfm+TiVJzzzzjF837Qsl0WeddZbr0qVLno/p2rWrT+4VT0EjOyX2Gs5c3PPftc31unXgRe8nHdxRwqdtpwNEei2igy0vvviiX/fLL7/cbdy40b300kv+dSuOguHyev8qwdN2V5xru7/33nuFGpqtx6r6r+2p98UTTzzhpywsXrzYr58oPnRwonbt2n7/ZWZm+n2n98Lu0HtF9DyFiSFRvIwcOdIn3XqPad20T3UgT+/lvn37+vtpHTV8Xu9pfUbp/joAowNP+nzSgQlNQYj1yiuvuK1bt/r1UHIa+x684IIL/LopnpTUqyeAbtfBB73vH3jgAf/Zo88tvdd0IKkw+zSg2NZ99BmsuNXn39lnn+0P1gSfuzrIqM9T/a111cEvbVN9dugzIYg5HdAJDlBoe6g/hz6ntU40kAPgRQAAu+2VV16J6CM156VcuXKRoUOHZrvvuHHj/G3Dhw/Pdv3o0aNzXV+/fn1/3dixY6PXrVy50i+3b9++0evuuOMOf793330317plZWX5n1999ZW/T/PmzSPbtm2L3v7444/763/55Zfodccee6y/7vXXX49eN3v2bH9dcnJy5Icffohe/+mnn/rrtQ0Cf/31V671GD9+vL/fsGHDcm23Dh06RHbu3Jnt/gMHDvS3rVq1KjJr1qxInTp1Im3atIn8+eef2e4XvC7df1eC7VShQoXIKaecErnnnnsikyZNynW/YJn77bdfZMOGDdHrR44c6a/XNpPt27dHatasGTnkkEMiW7Zsid7vo48+8vfT8wV69Ojhr+vfv3+u5zvttNP8vs7ptdde89tbMRNryJAhflnfffed/3vOnDn+fmeddVYkMzMz7v4viK5du/rlVqpUKbJkyZJcty9YsMDf/uCDD0amT5/ufw/W7emnn45UrFgxsnnzZv9atY1jKaYOPvjgSGEF++Ktt97K8z59+vTJti6ycePGSMOGDSMNGjSIbhPFWGzsy9q1ayP77rtv5NJLL41e9/777/vlDR48OHqdHtuxY8dcsR7EaSz9XbZs2cjcuXOj102dOtVf/+STT0av69KlS2SfffaJLFu2LHqd9mVqamquZcYTbGe9R3TR8917772RpKSkSMuWLQsVQ8F6674zZszI9VyKT8VprMcee8w/5j//+U/0Or0n2rVr52MheO8EcZOenu4/v2IF2++KK67Itq3r1q3rX8f999+fbV+lpaX51x1734Ls02AdqlWrlu0z5IMPPvDXjxo1KnrdMccc498DixYtyvO91KtXr0jt2rUjq1evznafCy+8MJKRkRH3MxDA3odh5AAQoqefftpXXXVR1U8VV50uRxWugCo/Gu6oucOqBgYXVapV0cw5/FHzJFVlCaiCoiGNqsQE3nnnHT98M6jgxco5HFVV2Nh5ksGyY5cnWhdVpgN6TlVpVX3SEPJA8Hvs44Oqr2j+rzpdawirHv/zzz/nWkdVpFJSUuJuUw1n1lB2VZc0XF3Do2OpsqY8Ib+qdkDVQ1W2Dj30UPfpp5/6YaHa7hoJoFEBOWkYflCtF1XwVen75JNP/N8TJ070FW9VL2Obgp122mmuWbNmuYYxi7o5F5RiRdtby4qNFVX6JIgVVSdVwdTIAI1aiFWY4cjBaAHFXH7D+UWjKlq2bBltlKbtqmpwzlELxUH7Q8PnNWIgNn5VldSoCo2mEMVYEPvaXn/++acfbaBh3LFxqeVp6kfsvtJjNSKioDQCQkOVA9pWGgodvE9UxVY8n3nmmdnmVet9ot4PBaWRIPpM0EWP1bQHDfNXJb4wMRTQe62gc7O1nTQyQ1XlgKrBGl2g0TaaHhJLlf28qvb6nIzd1tonel+rUhzQ50fOz76C7tPYCnrsZ0jOzz81tRw7dqwfIbT//vvHfS9pvfSZq5Ef+j12u6qirpEq8Z4bwN6HYeQAECJ94Y9tkKYvoUrsNMxQQ7L1pVCdr/VlLHaOcH6NunJ+4RN9WYyd360hjvoiWxA5lxd88cw5X1zJVs5ETQcJNN8z53U5H6/hpRpSrWGjmtf4v6LZ/+i156Thp3nRF1rNiVRynPN0UkWhfaKLhnpqmLWGgStR1PMosY9Nmps0aZLtsdoeSmiC+aqaZy1KAHJScqOhu7GUwO0qiY2lWNFBgLwSlCBWtP+VZO9OAysdONDBIs0x1nbRwaKc0x9y0jQCzaHVEGNNG1CilwjaD7EHgAJKMoPbg9OOaXi51llzcnUgKF4M6v46qJIz3uLt57zs6n2rfaf3ieIpp3jX5UXxquHNoqHZeh2xMVbQGCrIezEnbSe9R3Ie4Ind7gVdds7tpc8VvbbYKTDB9TlPU1eQfVrQz78g6c7vNHVKyDVMXnO/dYknEQ0XAZQ8JNsAsAfpS6iq25rDqy+9qgaq+qJEW/MP48n5pTivim9sAlsYBV1eXvcryONVAVSirXmLqrLpC7ISVVXK9fpziq2E56SDCPoyre2leZZhUZVRowt0UTVOz6EkU5W9PUXJUM7EJD/aVi1atPBdpePJeeCjqFRlVRVYFVbNNdccb83N1QGi2DnnOemghU6hppEJmh+8q7nhiaYDCJpPrGqyOrDrfah41oGhYJ5zWMJ+3+b3PKqihxVD+b0Xd1d+y463vQqyDQu7T8PYL8FnmA5G5TWPXyMZAIBkGwD2MA1pFA2rFA0t1fBRdcUO64utlqmqbEmhplT6EhrbOViNkeJ1c94VNWBTRThoNKVqatg0GkHJ9u+//57teh0gyfmFXM2ggi/S9evX9z9//fXX6LDcgK4Lbt+VvIZ6a7/q9FrqGJ/fcHDdTwmAhkvnbAhVEGrepWZdGnqsgxBDhgzx26R///7+97yoSqg4VkdtDblO1CmwtJ21vXNSpTO4PYhLdfPXtI7Y7anmejmXN2bMGP+eja1ux3uOolJSqMptzjMBSLzriqqgMVQU2k5qJqbYiz2IlHO770kF3acFpWVJfp+nOiCqzyIdpMrvQAcAMGcbAPYgDWlUh2YNHw+GVup0MvqSpnM+x0vMi5KQqvqrL9TBPM09WUkrCFWPcj6vOrPrdReWvkBrqKbmSyuBV4f1WAU99ZfuN378+Li3qYtwvGHCOsWaOhfHfrFXQh7MqVVCqqRJCWnsaZS0PA3d1dztgtDpiuINr1esaBj+Cy+8kOs2DUEOOrerqqdkR52sc44c2NX+Vxd5zfVWx2otR5Swa96tnjdnl/qc1DVaiU1h5jOHTadSU+fp2P2rbaO40Vz/YHh9UNWM3SZ6fTnjQsvTe1Ed9AOKXcVw2BVpzbdfvnx5tkQ7iMcwFDSGikLbacWKFf4UeQFtN20nHaTYk6NEAgXdpwWlRFqdznXWB3WOjxU8h55Tn7matx0vKdcwcwAQKtsAECJ9SQ6qOpqzp7nAqo6qQqiKoegLqIZDa5ijTiGlobcaxqz7qZmRhpwrsSwMDZ9UIqhTdKmxj5p+qVGQElMlgmqeVpw0/FinC9LwcSU6+uKran5wyqPCUiKp4aJKBpU8qDFTUEku6Km/lGzr9FA6XY9Ot6ThszqwoWRn3LhxftmaXx9Lpx5S0y01ldOpfnTqL82n1bBp0X7TKYl0u/arhlUHp/5Skqe5zAWh/aWE5cYbb/SnNVKiojnk3bt396dh0umn1MhKVWQlfYoxXa957Er4tU5q9qYDOGr4pFMZaci6TsGmoeGKtbwoSVYSkTORVDO54Lk1nzuv4bd63QVNqpSEKDnPSXNrdfq5/CixCd5bsXQARu8vNWrTQRAdJNB+00gFnVJPjwuqropLVUDVSFAHQnS73h+K0WDkiWjba1truZqfr9v1uHgHRHaH4lUH4/RcGhmgfatTAGq+sD4bwlDQGCoKTT3Qqbk0jHvSpEk+5vU5pKkIeq/ENhfcUwq6Tws70kPvezVO1GtUfCoO1PAw2C86RZm2p3oF6PNAz6fPXDVG02edfgcATv0FAHvo1F/ly5ePtG7dOvLss8/GPf3S888/Hzn88MP9qWx0mpkWLVpE+vXrF1m+fHm+p9sJTqOkS6w1a9ZEevfu7U9XpdMO6dQ5OkVOcGqavE6hFJwSJ/Z0Rnmdpimv9dHjr7nmmmyn3unZs2ekevXq/hRAnTt39qcO0+NjT9sTbLcJEybkWmbsqb8COp2O1k3LDE4/VtBTf+3YsSPywgsvRM4880y/Hjp9mk67dOihh/pTWcWePihY5htvvBEZMGCAP72X9pNee87TAcmIESP8crTMqlWrRrp16xZZunRptvvEOx1WYNOmTZGLLrooUrlyZf+8sacB06mUHnjgAb8/tPwqVar4uLnzzjsj69evz7acl19+Oboeup+21eeff57nNnnvvff88z300ENxb3/77bf97Y888kiuU3/lJ69Tf8U7PZ4unTp1ynNZwb7I6xKc0mrevHmRc889129DvfeOPPJIfwq2WHof6tRYwf7XttJ9tL45T72m91P37t396ap0Kif9Pnny5AKf+iv2/RDIGf8yZswYvx56zzZu3Djy4osv+tP66TXsSn4xFaugMZTXeuf33v/jjz+i73W9Bn2OxW6fXcVNvPd5fq8t52dTQfdpfusQ7/NDp7bTqfSCeGratGnk9ttvz/Xatb3q1asXKVOmTKRWrVo+lvXZDgCS9H8fMgAA4P9oDrKq5RppUNhRBsDu0iiLGTNm5OoZAACwhTnbAAAACaJ507GUYGuahM4fDwCwjTnbAAAACaLu15rzrJ86L7WasqmhYr9+/dgnAGAcyTYAAECCqFmfmrupq7ea2um89Pfee69r0qQJ+wQAjGPONgAAAAAAIWPONgAAAAAAISPZBgAAAAAgZMzZNiYrK8stX77cVapUySUlJSV6dQAAAACgRItEIm7jxo2uTp06Ljm5+OrNJNvGKNGuV69eolcDAAAAAExZsmSJq1u3brE9H8m2Mapoy4IFC1zVqlUTvTpAoezcudNNnTrVtWrVyqWm8vEDe4hhWEb8wjpiGEW1YcMGX7AMcqniwrddY4Kh4+np6f4CWPsnmZaW5mOXZBsWEcOwjPiFdcQwdldxT8OlQRoAAAAAACEj2QYAAAAAIGRJEbVmg6n5BhkZGW7dunX+J2CJPm62bNnih5LTTR8WEcOwjPiFdcQwdjeHWr9+fbFOxaWyDaBYlS1bli0O04hhWEb8wjpiGJaQbBuVmZmZ6FUAihS3EydOJH5hFjEMy4hfWEcMwxqSbQAAAAAAQkayDQAAAABAyEi2AQAAAAAIGd3IjaEbOax3EdV8q5SUFLqRwyRiGJYRv7COGEZR0Y0cwF5h+/btiV4FYLcQw7CM+IV1xDAsYRi5UXQjh9W4nTZtGvELs4hhWEb8wjpiGNakJnoFUDSdZ7/mXHpaqd98Ew65ItGrAAAAAACFRmUbAAAAAICQkWwDKFZqjgZYRgzDMuIX1hHDsIRh5EZlJjtHygJrUlNTXZs2bRK9GkCREcOwjPiFdcQwrKGybVRSJNFrABTtlB3r1q3zPwGLiGFYRvzCOmIY1pBsG5VMrgKjXURnz55NN3KYRQzDMuIX1hHDsIZkGwAAAACAkJFsAwAAAAAQMpJtoxhFDouSkpJcWlqa/wlYRAzDMuIX1hHDsIZu5EZl0Y0cRk/X0apVq0SvBlBkxDAsI35hHTEMa6hsG0U3cliUlZXlVq5c6X8CFhHDsIz4hXXEMKwh2TaKbuSw+k9y/vz5JNswixiGZcQvrCOGYQ3JNgAAAAAAISPZBgAAAAAgZCTbRtGNHFa7iGZkZNCNHGYRw7CM+IV1xDCsoRu5UXQjh9Uuos2bN0/0agBFRgzDMuIX1hHDsIbKtlE0SIPVxiZLly6lQRrMIoZhGfEL64hhWEOybRSn/oJF/JOEdcQwLCN+YR0xDGtItgEAAAAACBnJNgAAAAAAISPZNiqSlOg1AAovOTnZ1ahRw/8ELCKGYRnxC+uIYVhDN3KjspKcS0n0SgBF+CfZuHFjthvMIoZhGfEL64hhWEN5ySi6kcNqY5N58+bRjRxmEcOwjPiFdcQwrCHZNopu5LD6T3LVqlUk2zCLGIZlxC+sI4ZhDck2AAAAAAAhI9kGAAAAACBkJNtG0Y0cVhub1K1bl27kMIsYhmXEL6wjhmEN3ciNohs5LP+TBKwihmEZ8QvriGFYQ2XbqOSsRK8BUHiZmZlu1qxZ/idgETEMy4hfWEcMwxqSbaOSEr0CQBFEIhG3fv16/xOwiBiGZcQvrCOGYQ3JNgAAAAAAISPZBgAAAAAgZCTbhhukARYbmzRq1Ihu5DCLGIZlxC+sI4ZhDd3IjeLUX7D6T7JmzZqJXg2gyIhhWEb8wjpiGNZQ2TaKbuT/M3bsWHfqqae6GjVquKSkJH8ZMmRIgbbhqlWr3LXXXuvq16/vypYt66pXr+46derk5s+f728fNGhQdJnxLgsXLtyDe7j0dhGdOnUq3chhFjEMy4hfWEcMw5q9rrLdvXt317x5c3fLLbckdD2UEH788cdu1KhRRXo8o8j/5+eff3aff/65H5q8evXqAm8/3bdt27ZuwYIFPtE+8MADfYfL8ePHu+XLl/vl6XzQuk+sOXPmuD///NOVK1fOValSpUj7bm+mbbxlyxa6kcMsYhiWEb+wjhiGNXu8sp1fZVAXVQ9VIYy9rlKlSu7ggw9211xzjU9uctq+fbsbPHiwa9Wqldtnn318RbJ9+/bulVdecTt27MhzXVRR++STT9x1110Xve64446LPm/58uV90nXfffeFmgxo2e+//3626y699FKfKI4bNy6059kb6eDJhg0b3Kefflqox912220+0VacKf6mT5/uZsyY4datW+fatGnj73PZZZe5H374IXr56quvXEpKir/t4osvdhkZGf73X3/91f3973/3w6OVhCtJP+WUU9xPP/20B14xAAAAAAv2eLL9+++/Ry+PPfaYS09Pz3bdTTfdFL3vF1984a9TUnzvvfe6WbNm+YR6zJgx2RLtzp07u/vvv99dccUV7vvvv/dJjRLzJ5980idMedHt5513nqtYsWK26y+//HL/vEqaBgwY4O64444CD0UuKlVTL7roIvfEE0/s0ecp7apVq+bS0tIK9RgdSBk5cqT/vV69eu5vf/ubq1Chgo+1d955xyfM8bz66qt+6LkOnvTt2zd6fdeuXf0IhZ07d/rkPSsry40ePdrNnDlzN18dAAAAAKv2eLJdq1at6EWVQCUqsdfFJr5KnHSdhvCeccYZPvnWMN5evXpF53gqYdc8XSXgSrBbt27t76/E9ccff3RNmjSJux56/Ntvv+26dOmS6zZVx/W8mrvbs2dP17JlSz80Ob/KdOXKld3QoUOjBwB69+7tateu7avjWo6q49KgQQP/86yzzvLLCf4WrcuHH37oh9UWFt3Ii04J89q1a/3vSopVzdaQ8GnTpvk4Upzk2t5ZWe6RRx6J7remTZtGbwtGXyjh1mgFDUPXvG+NmkB2GhnQrFmz6AgBwBpiGJYRv7COGIY1ySW94+D111/vFi1a5CZNmuSvGz58uDvxxBPdoYcemuv+ZcqU8RXKeJRIrV+/3h1xxBH5Vjw1rHv27Nm+8lxQqk4raVa1VNVxrWOQVE+YMMH/1BB3Vc+Dv0XromqoDhLkZdu2bX6YdOzFryuTtotM2zyg+ftKjHXR7/LUU0/leswHH3wQTar/9a9/ZbstOIBz/PHH+2Wcc845PonXwRdkpwNOOlCln4BFxDAsI35hHTEMa0p0si2qgknQ+VkJT3BdYShh19GweKcdeuaZZ3yFXcOHjznmGF/FjJ3XvSuLFy/2FfUOHTr4qrZ+amixqEu2KMFQ9Tz4O6ioq9qvdcuLKuS6T3DRsGdJySrUy0cM7YPgYIqGjut3XfS7xOsy/tBDD/mfRx11lN+/sYYNG+beeOMNPw9fy1ZfgKuvvtrdeOONbPc4Bzp0wCn2gAdgCTEMy4hfWEcMw5oSn2wHjcqCSlhRG5dpqLaS6XgVtW7durkpU6a47777zje2uvXWW93RRx9d4GVfcskl/vEaWqwk/bPPPivwYzXf+K+//srzds0hV0U+uCxZsqTAy8b/6HReOkCjbRmMgNBBlWDEg5rq6aLfJedUBPUF0EViewwENBpC0wQ0z19THAYOHOiv1+/ILZgSAlhFDMMy4hfWEcOwpMSf+ktN0qRhw4b+p7qFa5h3YaljuZJaza/OOURcFeMDDjjA/66h4PpdFUwNVxcl6DmT/Niu54cddpjvbP3f//7XzzM///zz/WPjzf3NSaeRiq1256QDBHk17IJz7777ruvXr1+2Sqka3KkSrfn+GtI/b948P3pAw/gDd999t0+G1cQsiK1ly5b50Q85TwsXVLUVF0qq43VE137UqAONVgia9GnuPwAAAIC9U4mubGs4t+ZDKxkK5mirgZUS2smTJ+e6vxLgzZs3x12WGqnJrjpEazi55omrghkk2EqGYxM1DWXPWY1Wl/ULLrjAvfDCC27EiBG+q7USsKCSGu8onJLArVu3xp1/joLRHPYgmY5tgKbrlDznRYn4l19+6ZuYqVma9oMOkGh0g+ZeB+bOnevna8sNN9zg+wjkpKZ66kKuc3crvjRdQJ3y4839BgAAALB3KFGV7TVr1rgVK1b4RFbnPVbncZ3W6+OPP452L+7Tp4//W0OD77rrLj9/VuflnjhxonvggQfcSy+9FE2sYylhVgX622+/jXt7rH/+859+2UqYzz33XHfCCSf4xKldu3Y+ab755pt9Ah1Ql2o1w1LSrGTsrbfe8gmX5mmLmqWpe7rOBa4qtTpfB8OP1Um9cePGhd5WmUnO0c/5f0P4dclPvDnYov2hc2fnR9XsXQ1XUqzogl3T+1gVf7qRwypiGJYRv7COGIY1JaqyrcqiktYWLVq4/v37+87OmkcbW2lUsqrTcmno8HPPPeeHe7dp08ZXwDVf+pBDDslz+ZdddpkfVrwrVatWdRdffLEbNGiQr64//PDDfohwx44dfWVdVW8NFw4o2R88eLDvLq51UXKnJllBFVSP1zprGbFVbDXV0jm+gb1JYTr9AyURMQzLiF9YRwzDkqRIUTuOGaQmaWpipmHeqlInkub1qmL+22+/+TnjhRk2rfsf8d1jzqWnudJuwiFXJHoVECLNrdcoFB2YSk0tUQNrgAIhhmEZ8QvriGEUVZBDqeG0pv/ulZXtPU2dv3WaJs2tTTTNAde6FCbRBgAAAADYsNeVltQQqyQIOp0DAAAAAEqfvaqyDQAAAABAcSDZNkrdyAGLXUQ1X5tu5LCKGIZlxC+sI4ZhDck2gGK1fft2tjhMI4ZhGfEL64hhWEKybVTKXtNDHqWJzlmu0/nt6tzlQElFDMMy4hfWEcOwhmQbAAAAAICQkWwDAAAAABAykm0AxYrmaLCOGIZlxC+sI4ZhyV53nu3SIjPZuZRErwRQSKmpqa5NmzZsN5hFDMMy4hfWEcOwhsq2UUk0SINBkUjErVu3zv8ELCKGYRnxC+uIYVhDsm1UMrkKjHYRnT17Nt3IYRYxDMuIX1hHDMMakm0AAAAAAEJGsg0AAAAAQMhIto1iFDksSkpKcmlpaf4nYBExDMuIX1hHDMMaupEblUU3chg9XUerVq0SvRpAkRHDsIz4hXXEMKyhsm0U3chhUVZWllu5cqX/CVhEDMMy4hfWEcOwhmTbKLqRw+o/yfnz55NswyxiGJYRv7COGIY1JNsAAAAAAISMZBsAAAAAgJCRbBtFN3JY7SKakZFBN3KYRQzDMuIX1hHDsIZu5EbRjRxWu4g2b9480asBFBkxDMuIX1hHDMMaKttG0SANVhubLF26lAZpMIsYhmXEL6wjhmENybZRnPoLFvFPEtYRw7CM+IV1xDCsIdkGAAAAACBkJNsAAAAAAISMZNuoSFKi1wAovOTkZFejRg3/E7CIGIZlxC+sI4ZhDd3IjcpKci4l0SsBFOGfZOPGjdluMIsYhmXEL6wjhmEN5SWj6EYOq41N5s2bRzdymEUMwzLiF9YRw7CGZNsoupHD6j/JVatWkWzDLGIYlhG/sI4YhjUk2wAAAAAAhIxkGwAAAACAkNEgzajPD+rhKleunOjVAArd2KRu3bp0I4dZxDAsI35hHTEMa5IikUgk0SuBgtuwYYPLyMhw69evd+np6Ww6AAAAACiBORTDyI3KzMxM9CoARYrbWbNmEb8wixiGZcQvrCOGYQ3JtlEMSIDVuNURReIXVhHDsIz4hXXEMKwh2QYAAAAAIGQk2wAAAAAAhIxk23A3RsBi3DZq1Ij4hVnEMCwjfmEdMQxrOPWXUSTbsBq3NWvWTPRqAEVGDMMy4hfWEcOwhvKoUXQjh9W4nTp1KvELs4hhWEb8wjpiGNaQbBtFN2dYjdstW7YQvzCLGIZlxC+sI4ZhDck2AAAAAAAhI9kGAAAAACBkJNtGpaSkJHoVgCLFbbNmzYhfmEUMwzLiF9YRw7CGbuRGJSUlJXoVgCLFbeXKldlyMIsYhmXEL6wjhmENlW2jdu7cmehVAIoUtxMmTCB+YRYxDMuIX1hHDMMakm0AxYrT1sE6YhiWEb+wjhiGJSTbAAAAAACEjGQbAAAAAICQJUV0dniYsWHDBpeRkeHWrVvnfwKW6ONmy5YtLi0tjSZ/MIkYhmXEL6wjhrG7OdT69etdenq6Ky5UtgEUq7Jly7LFYRoxDMuIX1hHDMMSTv1l1PZxR7qdFbMSvRolVupxcxK9CsijqcnEiRPdEUcc4VJT+fiBPcQwLCN+YR0xDGuobAMAAAAAEDKSbQAAAAAAQkayDQAAAABAyOhGbrST3upRjVxGRY6V5IU52yW3i6jmW6WkpNCNHCYRw7CM+IV1xDCKim7kAPYK27dvT/QqALuFGIZlxC+sI4ZhCaVRo7JoJA+DVNWeNm2a/wlYRAzDMuIX1hHDsIZkGwAAAACAkJFsAwAAAAAQMpJtAMVKzdEAy4hhWEb8wjpiGJbQjdxoJ701HzVy6RU4VpIXupEDAAAAELqRo1AiETYYbJ6yY926df4nYBExDMuIX1hHDMMaSqNG0Y0cVruIzp49m27kMIsYhmXEL6wjhmENyTYAAAAAACEj2QYAAAAAIGQk22Yx5xX2JCUlubS0NP8TsIgYhmXEL6wjhmEN3ciNoRt5wdCNHAAAAIDQjRyFkhWhMgh7srKy3MqVK/1PwCJiGJYRv7COGIY1DCM3KuJSEr0KQJH+Sc6fP59kG2YRw7CM+IV1xDCsIdkGAAAAACBkJNsAAAAAAISMZNssupHDZhfRjIwMupHDLGIYlhG/sI4YhjV0IzeGbuQFQzdyAAAAAEI3chQK3chhtbHJ0qVLaZAGs4hhWEb8wjpiGNYwjNwoupHDIv5JwjpiGJYRv7COGIY1JNsAAAAAAISMZBsAAAAAgJCRbBuV5LISvQpAoSUnJ7saNWr4n4BFxDAsI35hHTEMa1ITvQIomuQkJdskLLD3T7Jx48aJXg2gyIhhWEb8wjpiGNaQrRmVFWHXwWZjk3nz5tGNHGYRw7CM+IV1xDCsIWMzKsKug9F/kqtWrSLZhlnEMCwjfmEdMQxrSLYBAAAAAAgZyTYAAAAAACEj2TYqyWUmehWAIjU2qVu3Lt3IYRYxDMuIX1hHDMMaku0EuuSSS9yZZ55ZpMcmJ0VCX5+9ycMPP+yOO+44V7t2bVeuXDlXv35916NHDzd//vxdPvbNN990hx12mEtLS3NVq1Z15557rm/6ldPYsWPdySef7KpUqeLKly/vGjRo4K6//nq3N+OfJKwjhmEZ8QvriGGU6mRbyWFSUlKuy9y5c4u8Al9//bVfxrp163Z5XyVH8Z5/586dbm+TGUlJ9CqY9uSTT/pkuHLlym6//fZzixcvdsOGDXPt27d3GzZsyPNxL730kuvataubPHmyT9QzMzPdO++8444++mi3YsWK6P1GjhzpTjjhBPfpp5+6lJQUd9BBB/lY/eSTT9zeTNtr1qxZ/idgETEMy4hfWEcMo9RXtlWp+/3337NdGjZs6IrL5Zdfnuv5U1OLdrrw7du3u+KwY8eOPbDUpD2wzL2H4mjhwoU+8VM1u0+fPv56JcxjxozJM1769+/vfz/nnHP84/T4SpUquZUrV7p7773X37Z582Z31VVX+X8I/fr188v8+eef3YIFC/zPwK+//ur+/ve/u5o1a/rquoZXn3LKKe6nn35ypVUkEnHr16/3PwGLiGFYRvzCOmIYpT7ZVlJQq1atbJfHH3/ctWjRwlWoUMHVq1fPXX311W7Tpk3RxyxatMh16dLFD6fVfQ4++GBf4VOyc/zxx/v76DZV/lQ9z88+++yT6/kDqjBq2VpHDdnVUOFYuu6uu+5yF198sUtPT3dXXHGFHwLcu3fv6H2UdGk9Zs+eHU2wtM5ffPGF/3v06NGuQ4cOviJarVo1d/rpp2cbQqzXpMePGDHCHXvssX748PDhw33ideONN0YfpySMhCNxbr31Vrf//vtH/+7YsWP0d8VPPBMmTHCrV6+OJttSp04dd9RRR0VjQxQrf/75p//9jz/+8Em09rkSa/0dUIV81KhRfmSG4lans9AyZs6cuUdeMwAAAABjc7Y1f+KJJ55wM2bMcK+++qr78ssvfTIZuOaaa9y2bdv8sN1ffvnFPfDAA65ixYo+MVeCHFT5VKVW4l4UkyZNcueff7678MIL/XMMGjTI3X777W7o0KHZ7vfQQw+5Vq1a+WHAul0JsYayB7755htXvXr16HVKsFSZ1jDhoGqppHnixIm+AqrXftZZZ+U6b7AqoJqfq8pn586dfeKvdXn55Zfdt99+65Ox9957r0ivFeHSgZDnn3/e/96oUSPXqVOnuPdbsmRJ9HdVowP77ruv/6mh6EEsBzQ0XfG0ZcsWn1hrKoQquzJnzhz/U9er4r18+XJfLdd9AAAAAOxlyfZHH33kE+Xgct555/lqsCrUqhxrnurdd9/t56wGlIRoLqyq30pmVA0+5phj/FxWNZgKkhdVqTMyMvJ9/meeeSbb8/ft29df/8gjj/gkSQn0gQce6Cvkqlg/+OCD2R6v9dNjGjdu7C9KbFRJXLVqlVu7dq3/XUlykGzrZ5s2bXxFPahonn322e6AAw5wrVu39smzkvuc1UhtE91PQ+w1t/exxx5zAwYM8Nc1b97cDRkyZJevVXSQQnOIYy9CN/Jw6OCJDpZobrXiT4lvXpXtvOQcoRDbQ+Df//63mz59ul++LFu2LHqQRaM9RO8dxYRiS5VtxUtppYNT+gzQT8AiYhiWEb+wjhiGNYX+xqvEYMqUKdGLKtoaNqtEV42mNH+1e/fubs2aNe6vv/7yj7nuuut8Aq6Ee+DAgW7atGn5PoeGXccm1OPGjYve1q1bt2zPrwRWVEHW8mPpb1UPY5sxHXHEEdnuc8ghh/iEXxVtPc+hhx7qDwbob9HP2Eqjlqfhv0oYNBRdBxhiq5rxnkeVTFXt27ZtG71O88xzrks89913n0/Kg4tGAwjdyHef5lJrZIMSbB2g+e6773wjs7wE2140Rzvn78GwdL0PAjpQI0ceeWS2qQZB1fuNN95wl156qatRo4afWqEpGBo5UZr/SerAGsk2rCKGYRnxC+uIYZT6ZFvzl1XVDS6qvCo5bdmypR8SruHcTz/9dLYGZJdddpkfHqskXFVgJZnqBp0XzW2NTahjk1IlnLHPryG6hV3/WJpfrSq7KthBYq3XoteliuT333/vE7KAqpEaAv7CCy+4H3/80V9iX2tez1NUOpigZD24BEOZ6Ua+ezTlQXOtFa+arz1+/Hh/ACWWDiA1a9YsekBHibPmXksw/UFDv3/44Ydo88Bg9ESQTGq6QexPadKkif+pgzuqqmuUg6ZY6ECU6PfSSge+pk6dSjdymEUMwzLiF9YRw7Bmt8dyKlnRfGXNSVbyogqhEpB4VcErr7zSvfvuu34Yt5JVKVu2rP8ZW31WdTw2odb5jHdFw3BVmYylv7U+Gq6en2Deti5KtpUoKQHXEHQl3UHFXNV6zce97bbbfCKm59TQ813RAQINDQ4S82CosbbdrmhIsyrosZf/oRv57tBwfjXuk40bN7pTTz3Vx68uL774or9eje+CXgJBrAYdx5VsKzlXDOjxOugTdCpXrAdN9zStQdMnTjrpJP+3Kudqyic6+KTGgE2bNvUjKu644w5/vQ72lFYacq/56zQHhFXEMCwjfmEdMQxrinbOrBhKhtVATJVqVX2V4KpSl3P+sk5ppMRXyelXX33lkxSpX7++ry5rLrgSHiXWGjpeWErgVXlUt/ELLrjAVyqfeuopP8d7V5Rg33DDDT6ZUqfx4LqbbrrJLzOoUisxUmVTzbSUPGvoeJBg7Yrmgd9///2+qqlqqeaYF+Tc4tgzdBAloNETsYIKdTzqYK94UKM9TV1Qt3kl7tq36kweePTRR/3fStx/++03P7T8tNNO83O4gznhPXv29HO0NepD3fs1Z1zvEy0LAAAAwF6ebKuztxJHdRjXcFtVhDXPWKfXCqhqrY7kS5cu9ZVZJTNKRkRJyJ133umTViUfelzODuIFcdhhh/mmbKoOKuFWMqzEZlenEhNVHnVKLh0MCBJ9Jdta79j52qp4v/nmm34OuuZ6qyKpOesF6R6tgwGqkPbo0cMvR/N0NYQ46EyN4hXMmy7KfdQ3QJf8aB/ffPPN/pIXxakuAAAAAEqfpAjjOU1RN3INS189qpHLqEhH57ykHve/02qhZNHHjQ4wKYY1ogWwhhiGZcQvrCOGsbs5lL6H/v9puQYq20gM8hRYpARbo0gAq4hhWEb8wjpiGNZQGjUqM8JxEtijxoATJkzIdi5ywBJiGJYRv7COGIY1JNsAilXsmQcAi4hhWEb8wjpiGJaQbAMAAAAAEDKSbQAAAAAAQkaybVSyY84r7ElJSXEtW7b0PwGLiGFYRvzCOmIY1pBsAyhWZcuWZYvDNGIYlhG/sI4YhiUk20ZlcdY2GG1qMnHiRJqbwCxiGJYRv7COGIY1JNsAAAAAAISMZBsAAAAAgJCRbAMAAAAAELKkSCQSCXuh2HM2bNjgMjIy3OpRjVxGRY6V5CX1uDmEYQmkjxvNt1I30aSkpESvDlBoxDAsI35hHTGM3c2h1q9f79LT011xIVsDUKy2b9/OFodpxDAsI35hHTEMS0i2jaIbOSxSVXvatGl0I4dZxDAsI35hHTEMa0i2AQAAAAAIGck2AAAAAAAhI9kGUKzUHA2wjBiGZcQvrCOGYQndyI120lvzUSOXXoFjJXmhGzkAAAAAoRs5CoUTtsHqKTvWrVvnfwIWEcOwjPiFdcQwrKE0ahTdyGG1i+js2bPpRg6ziGFYRvzCOmIY1pBsAwAAAAAQMpJtAAAAAABCRrJtFnNeYU9SUpJLS0vzPwGLiGFYRvzCOmIY1tCN3Bi6kRcM3cgBAAAACN3IUShZESqDsCcrK8utXLnS/wQsIoZhGfEL64hhWMMwcqMiLiXRqwAU6Z/k/PnzSbZhFjEMy4hfWEcMwxqSbQAAAAAAQkayDQAAAABAyEi2zaIbOWx2Ec3IyKAbOcwihmEZ8QvriGFYQzdyY+hGXjB0IwcAAAAgdCNHodCNHFYbmyxdupQGaTCLGIZlxC+sI4ZhDcPIjaIbOSzinySsI4ZhGfEL64hhWEOyDQAAAABAyFLZojalHD3epVatmujVAAAAAADEQWXbqORkdh1sxm2NGjWIX5hFDMMy4hfWEcOwhm7kxiSqkx4AAAAAWLQhQTkU5VHDDSIAi3E7b9484hdmEcOwjPiFdcQwrCHZNopkG1bjdtWqVcQvzCKGYRnxC+uIYVhDsg0AAAAAQMhItgEAAAAACBnJtlF0I4fVuK1bty7xC7OIYVhG/MI6YhjW0I3cGLqRAwAAAEDB0Y0chZKZmckWg8m4nTVrFvELs4hhWEb8wjpiGNYwjNyoSCSS6FUAihS3Or8h8QuriGFYRvzCOmIY1pBsAwAAAAAQMpJtAAAAAABCRrJtFN3IYTVuGzVqRPzCLGIYlhG/sI4YhjWpiV4BFA3JNqzGbc2aNRO9GkCREcOwjPiFdcQwrKGybRTdyGE1bqdOnUr8wixiGJYRv7COGIY1JNtG0c0ZVuN2y5YtxC/MIoZhGfEL64hhWEOyDQAAAABAyEi2AQAAAAAIGcm2USkpKYleBaBIcdusWTPiF2YRw7CM+IV1xDCsoRu5UdtvucVtK1cu0asBFFqa4pftBsOIYVhG/MI6YrjkKvfII4lehRKHyrZRmcnsOtiM2ynNmxO/MIsYhmXEL6wjhmENGRuAYpXJFAgYRwzDMuIX1hHDsIRkGwAAAACAkJFsAwAAAAAQMpJto5KzshK9CkCR4vagOXOIX5hFDMMy4hfWEcOwhmQbQLEqu2MHWxymEcOwjPiFdcQwLCHZNiqLbuQwGrdTDjqI+IVZxDAsI35hHTEMa0i2AQAAAAAIGck2AAAAAAAhI9kGAAAAACBkJNtG0Y0cVuO29cyZxC/MIoZhGfEL64hhWEOyDaBYbS9Thi0O04hhWEb8wjpiGJaQbBtFN3JYjduZTZoQvzCLGIZlxC+sI4ZhDck2AAAAAAAhI9kGAAAAACBkJNsAilVKZiZbHKYRw7CM+IV1xDAsSU30CqBoUrKy2HQwGbetZ81K9GoARUYMwzLiF9YRw7CGyrZRkUSvAFDEuF1fsSLxC7OIYVhG/MI6YhjWkGwbRTdyWI3buQ0aEL8wixiGZcQvrCOGYQ3JNgAAAAAAISPZBgAAAAAgZCTbVkWYtQ2DIhFXfutW4hd2EcOwjPiFdcQwjKEbuVEpJNswGrcHz52b6NUAiowYhmXEL6wjhmENlW2jOPEXrMbt6ipViF+YRQzDMuIX1hHDsIZk26hIMrsONuN20X77Eb8wixiGZcQvrCOGYQ0ZGwAAAAAAISPZBgAAAAAgZCTbVtEgDRZFIi590ybiF3YRw7CM+IV1xDCMoRu5UXQjh9W4bbJwYaJXAygyYhiWEb+wjhiGNVS2jcpKSkr0KgBFitvlNWsSvzCLGIZlxC+sI4ZhDcm2URGSbRiN299r1iR+YRYxDMuIX1hHDMMakm0AAAAAAEJGsg0AAAAA2KMefvhhd9xxx7natWu7cuXKufr167sePXq4+fPn+9s3btzo+vTp4w4//HBXvXp1l5aW5g488EB3++23+9t2ZeHChe6SSy7xyy1fvrxr2rSpGzx4sMvKyoreZ+vWre7iiy92zZo1c8nJyS4pKckdddRRe+w175XJtnaydqRlSXQjh9G4rbZ2LfELs4hhWEb8wjpi2LYnn3zSjR071lWuXNntt99+bvHixW7YsGGuffv2bsOGDW7NmjXu8ccfdzNmzHB169Z1FStWdHPmzHF33323u+CCC/Jd9qpVq9yRRx7pXn31Vffnn3/6ZHrevHnu5ptvdjfeeGO2ZPu1115zmzZtcunp6Xv8NZfKZFtHNM4888xs17399tv+CIeOqJQGySTbMBq3DZYtI35hFjEMy4hfWEcM23b55Zf76vOsWbN8NTsofq5YscKNGTPG52oPPvigT5ynTJnilixZEq06//e//3Vr167Nc9lvvfWWf5z88MMP/vHPPvus//upp55yS5cu9b9XqlTJLV++3P/dunXrPJenZXTq1MlVq1bNr1eDBg18fqkE3u3tyXZOL774ouvWrZvf4H379nWlAd3IYTVuF+63H/ELs4hhWEb8wjpi2LZbb73V7b///tG/O3bsGP1dw8pr1arlbrrpJp8Qi5LcNm3a+N815Ds1Ne+zVscOFdd9Y39mZma6cePG+d9TUlL8MPb8aFmnn366+/LLL12ZMmVc8+bN3V9//eU++OADfwCgMEp9sq1x+tdee6178803Xc+ePePeR0dJNHa/SpUqbp999nGnnHKKH7IQGDp0qB/u8P7777smTZr4Hd+5c+dsG3vQoEH+6Mhzzz3n6tWr55dz/vnnu/Xr1/vbNWRCO0tHbmLpiE5soBUU3chhkeJ2TZUqxC/MIoZhGfEL64jh0iMzM9M9//zz/vdGjRr5KnJOK1eudO+8847//cILL4wm4fGceuqpfti5tG3b1udlV155ZfR2VbMLSrmhhrTLpEmT3OTJk/26TJ8+3R100EGFeJWlPNnWGP277rrLffTRR+6ss87Kd9j5xIkT3YcffujGjx/vIpGI32E7duyI3kdHM+655x4/r+C7775z69at8zs91ty5c93IkSPdqFGj3OjRo/2Oufrqq/1txxxzjA8kzREIaPnDhw93l156aZ7rtm3bNj+HIfYCAAAAABZt3rzZ52affvqpr2Yrd1JlO5aGa3fo0MEnyZrTPWTIkHyXqTzrs88+c8cff7yvaOtxyvHUAE1U9CwoDR1v166d//2AAw5wLVq0cF27dvW5nRq3FUapTbY1rl9VbZX74x0pCaiCrSRbQ81VYW7VqpVPgJctW+Yr2bGJscb7a8OrQ54m33///ffup59+yjbhXsm4jqQouVYTAFXUg2p2r1693CuvvBK9vwJLj1EFPC/33Xefy8jIiF5UNQcAAAAAa1asWOGOPfZYnwep07iKmDmrxSp+aq628rQuXbr4JDq/qnZAeZqGfqsoqkq0CpoqoopGJxeG5pArP1SSXaFCBd//q3v37oXu/1Vqk+2WLVv6iewDBw703ebyogn6Gv+v4QaxRzPUKl63BXSfYM6AqMOdhpbH3kdzENRZL3aHa8z/r7/+6v/W0RVVvzXhPhierkRbOzAvAwYM8EPRg0swdJ1u5LBIcVt75UriF2YRw7CM+IV1xLBtM2bM8Em0hmaryKmkWhXpWEpqTzjhBLd69Wo/FVjFT03PjaWiqHIxXd57773o9d9++60fnh4MBdf8b1E1Wgl+QSlBV1FVudvLL7/sczcVTYOpwYWR9yxz45T0amdpKMHJJ5/sK90FOSKyJ9WsWdMfnVF1u2HDhn6dvv7663wfoyEVOYdVCN3IYZHits7KlYleDaDIiGFYRvzCOmLYtrPPPtstWrTI/67zZmvabuCyyy7zf6sQqWS3bNmyfgTx0UcfHb3PM8884w477DA/4jgoZgb9sURztJWIaySwhqFrGrAaomkIemzCrqHhovuKOpcH133zzTdu3333dSeeeKLPHbUsDUufOXNmtKBbGKW2si06obk2mIYrKOGOdzJ0dZfbuXOn+/HHH6PXaUK8dmDskAbdR/O6A7pdQxT0+IDOFRc7+V5HQbRzVCWPDaQRI0b4hgCNGzf2cxCKIvP/5h8Alihu5zRoQPzCLGIYlhG/sI4Ytm3btm3R35XgKv8KLjoV1/bt26PDvvV77O267Kp31UknneTPna08TaOS9beGlZ9zzjnZ7qdEXBdN5w3WK7hOibwSdCXuKo4qIdfIZI2YVqX8jjvuKNRrLrWV7YCORqh6rAq3OoircVksjd8/44wz/Hnf1ElcRzD69+/vK+O6PqBJ9RrK8MQTT/id17t3bz8MQidPD6hLeY8ePdxDDz3kg+G6667zR2c08T+gdVAQ6OTs//73v4v+wki2YVFSktugTpGKX84VD4uIYVhG/MI6Yti0hQsX7vI+QbKdHyW+8e73yCOP+EsYzxGco3t3lerKdqBu3bo+4dbYfyW7OY+KaFi3mp7pfGqaZ60d8Mknn2TrWqehB+puftFFF/lqtFrLq0IdS8MPNDxCQyB0JEXDDDTcIZYq3Rr/r/kEOt0YAAAAAKD0KZWVbTUey0mV6t9++y3u/XV+bXUR3xUl0rrk56qrrvKX/Gg4ghLyXZ1QHQAAAABgU6lMtksqTeD/5Zdf3Ouvv+5PN7Y7krKyQlsvoLgobusvW0b8wixiGJYRv7COGIY1JNvFSHPA1VVPE+7/9re/7day9orx/yh1FLfV165N9GoARUYMwzLiF9YRw7AmKVKQGeIoMTTfPCMjw/1+zTWuStmyiV4doNBdRGc3buyazZvnUvjogUHEMCwjfmEdMVyylStAc7JE51Aaaaxm1cWFAqlVdCOHRUlJbmv58sQv7CKGYRnxC+uIYRhDsg0AAAAAQMhItgEAAAAACBnJtlHJdCOH0bg9YOFC4hdmEcOwjPiFdcQwrKEbuVFJiV4BoIhxm7FpE9sOZhHDsIz4hXXEMKyhsm1UZjK7Djbjdkrz5sQvzCKGYRnxC+uIYVhDxgagWGWmpLDFYRoxDMuIX1hHDMMSkm0AAAAAAEJGsg0AAAAAQMhIto2iGzmsxu1Bc+YQvzCLGIZlxC+sI4ZhDck2gGJVdscOtjhMI4ZhGfEL64hhWEKybVQW3chhNG6nHHQQ8QuziGFYRvzCOmIY1pBsAwAAAAAQMpJtAAAAAABItgEAAAAAKNmobBtFN3JYjdvWM2cSvzCLGIZlxC+sI4ZhDck2gGK1vUwZtjhMI4ZhGfEL64hhWEKybRTdyGE1bmc2aUL8wixiGJYRv7COGIY1JNsAAAAAAISMZBsAAAAAgJCRbAMoVimZmWxxmEYMwzLiF9YRw7AkNdErgKJJycpi08Fk3LaeNSvRqwEUGTEMy4hfWEcMwxoq20ZFEr0CQBHjdn3FisQvzCKGYRnxC+uIYVhDsm0U3chhNW7nNmhA/MIsYhiWEb+wjhiGNSTbAAAAAACEjGQbAAAAAICQkWxbFWHWNgyKRFz5rVuJX9hFDMMy4hfWEcMwhm7kRqWQbMNo3B48d26iVwMoMmIYlhG/sI4YhjVUto3ixF+wGrerq1QhfmEWMQzLiF9YRwzDGpJtoyLJ7DrYjNtF++1H/MIsYhiWEb+wjhiGNQwjN6rsnXe6clWrJno1gELZuXOnS5440ZW9+GKXmsrHD+whhmEZ8QvriGFYQ3kUAAAAAICQkWwblZSUlOhVAIoUtxkZGcQvzCKGYRnxC+uIYViTFInQ1tqSDRs2+GRl/fr1Lj09PdGrAwAAAAAl2oYE5VBUto3KyqIfOWzG7dKlS4lfmEUMwzLiF9YRw7CGZNsokm1YxD9JWEcMwzLiF9YRw7CGZBsAAAAAgJCRbAMAAAAAEDKSbaOSk9l1sBm3NWrUIH5hFjEMy4hfWEcMwxq6kRtDN3IAAAAAKDi6kaNQaJAGq3E7b9484hdmEcOwjPiFdcQwrGEsslEk27Aat6tWrSJ+YRYxDMuIX1hHDMMakm0AAAAAAEKWGvYCsWdFIpHovIPUVHYfbNm5c6fbvHkz8QuziGFYRvzCOmIYRaXcKTaXKi5ka8asWbPG/2zYsGGiVwUAAAAAzNi4caPLyMgotucj2TamatWq/ufixYuLNVCAsI4q1qtXzy1ZssSlp6ezUWEOMQzLiF9YRwyjqFTRVqJdp04dV5xIto2eX1uJNskKrFLsEr+wjBiGZcQvrCOGURSJKFTSIA0AAAAAgJCRbAMAAAAAEDKSbWPKlSvnBg4c6H8C1hC/sI4YhmXEL6wjhmFNUqS4+58DAAAAAFDKUdkGAAAAACBkJNsAAAAAAISMZBsAAAAAgJCRbBvy9NNPuwYNGrjy5cu7tm3bup9++inRqwQUyKBBg1xSUlK2S7Nmzdh6KLHGjh3runTp4urUqePj9f333892u9qd3HHHHa527douLS3NnXjiiW7OnDkJW1+gMPF7ySWX5PpMPvnkk9mIKBHuu+8+16ZNG1epUiVXs2ZNd+aZZ7pff/012322bt3qrrnmGletWjVXsWJFd84557g//vgjYesM5IVk24gRI0a4G2+80Xci//nnn12rVq1c586d3cqVKxO9akCBHHzwwe7333+PXr799lu2HEqszZs3+89ZHeSMZ/Dgwe6JJ55wQ4YMcT/++KOrUKGC/0zWF0CgpMevKLmO/Ux+4403inUdgbx88803PpH+4Ycf3Oeff+527NjhTjrpJB/XgRtuuMGNGjXKvfXWW/7+y5cvd2effTYbFSUO3ciNUCVbR/meeuop/3dWVparV6+eu/baa13//v0TvXrALivbqqxMmTKFLQVzVPV77733fHUlqGqrYti3b1930003+evWr1/v9t13Xzd06FB34YUXJniNgbzjN6hsr1u3LlfFGyiJVq1a5SvcSqqPOeYY/3lbo0YN9/rrr7tzzz3X32f27NmuefPmbvz48e6oo45K9CoDUVS2Ddi+fbubNGmSH6YYSE5O9n/rQwWwQENslaA0atTIdevWzS1evDjRqwQUyYIFC9yKFSuyfSZnZGT4g6J8JsOKr7/+2icwTZs2dVdddZVbs2ZNolcJiEvJtVStWtX/1HdiVbtjP4M1NW3//ffnMxglDsm2AatXr3aZmZm+ahJLf+sLH1DSKQlRxW/06NHu2Wef9clKx44d3caNGxO9akChBZ+7fCbDKg0hHzZsmBszZox74IEHfMXwlFNO8d81gJJEIzn79Onj2rdv7w455JDoZ3DZsmVd5cqVs92X78UoiVITvQIASj99iQu0bNnSJ9/169d3I0eOdL169UrougHA3iZ2qkOLFi3853Ljxo19tbtTp04JXTcgluZuT58+nT4vMIvKtgHVq1d3KSkpubos6u9atWolbL2AotLR6AMPPNDNnTuXjQhzgs9dPpNRWmh6j75r8JmMkqR3797uo48+cl999ZWrW7duts9gTbFU34FYfC9GSUSybYCGyhx++OF+uFfssBr93a5du4SuG1AUmzZtcvPmzfOnTQKsadiwof+yF/uZvGHDBt+VnM9kWLR06VI/Z5vPZJQEakKpRFuN/b788kv/mRtL34nLlCmT7TNYpwZTLxg+g1HSMIzcCJ32q0ePHu6II45wRx55pHvsscf8KRB69uyZ6FUDdkkdm3XOVw0d1+k5dAo7jdbo2rUrWw8l9oBQbJVPfQbUTV8NetSER3MI7777btekSRP/RfD222/3DQBjOz4DJTF+dbnzzjv9eYl10EgHPvv16+cOOOAAf/o6oCQMHVen8Q8++MCfazvok6FGlGlpaf6npqDpu7HiOT093Z+dR4k2nchR0nDqL0N02q8HH3zQf+i0bt3an+NVc18BC/MDx44d6ysnOl1Hhw4d3D333OPnCAIlkeauHn/88bmu10FPNftT5UUHjZ5//nk/lFEx/cwzz/jpEUBJjl81qdRBocmTJ/vY1UEincP4rrvuytX0D0jU6erieeWVV/xp62Tr1q3+9Is6P/y2bdv8gSJ9BjO9EiUNyTYAAAAAACFjzjYAAAAAACEj2QYAAAAAIGQk2wAAAAAAhIxkGwAAAACAkJFsAwAAAAAQMpJtAAAAAABCRrINAAAAAEDISLYBAAAAAAgZyTYAAAYsXLjQJSUluSlTphR5Gccdd5zr06ePS5RjjjnGvf76686yQYMGudatWxfovkOGDHFdunTZ4+sEACiZSLYBACikSy65xJ155pklLhHP7zJ06FD37rvvurvuuish6/jhhx+6P/74w1144YVub3HppZe6n3/+2Y0bNy7RqwIASIDURDwpAAAIT7169dzvv/8e/fuhhx5yo0ePdl988UX0uoyMDJeWlpawzf7EE0+4nj17uuTkvec4f9myZd1FF13kX3vHjh0TvToAgGK29/zHAwCgmHzzzTfuyCOPdOXKlXO1a9d2/fv3dzt37ozerkS4Q4cOrnLlyq5atWru9NNPd/Pmzcu2jJ9++skdeuihrnz58u6II45wkydPzvP5UlJSXK1ataKXihUrutTU1GzXKdHOOYy8QYMG7u6773YXX3yxf0z9+vV9BXrVqlXujDPO8Ne1bNnSTZw4Mdvzffvttz551DKV6F933XVu8+bNea6flvfll19mG1IdiUT8kOz999/fb6c6der45QS2bdvmbrrpJrfffvu5ChUquLZt27qvv/4623K/++47/5r22WcfV6VKFde5c2e3du3a6OO1vJo1a/ptqO09YcKE6GO1LFX8x4wZ47evlnH00Ue7X3/9Ndtz3H///W7fffd1lSpVcr169XJbt27NdruWo32tddT+bN++vVu0aFH0dr1mbdMtW7bkuX0AAKUTyTYAACFatmyZO/XUU12bNm3c1KlT3bPPPuteeukln9QGlJjeeOONPolVsqdq71lnneWysrL87Zs2bfIJ+EEHHeQmTZrkk1IlnnvCo48+6hNEJfOnnXaa6969u0++//GPf/gh0I0bN/Z/KzkWHRQ4+eST3TnnnOOmTZvmRowY4ZPv3r175/kcul3JbPPmzaPXvfPOO/65n3vuOTdnzhz3/vvvuxYtWkRv1/LGjx/v3nzzTf885513nn9e3Vc0d71Tp05+G+l+eg4ltpmZmf72fv36+ed49dVX/es44IADfDL+559/Zlu3W2+91T388MN+X+gAhYZ+B0aOHOm3/b333utv14GTZ555Jnq7DqBoOsGxxx7r11HrccUVV/gkPqBEXvf78ccfd3NPAQDMiQAAgELp0aNH5Iwzzoh72y233BJp2rRpJCsrK3rd008/HalYsWIkMzMz7mNWrVqlTDbyyy+/+L+fe+65SLVq1SJbtmyJ3ufZZ5/195k8efIu12/gwIGRVq1a5br+2GOPjVx//fXRv+vXrx/5xz/+Ef37999/989x++23R68bP368v063Sa9evSJXXHFFtuWOGzcukpycnG19Yz366KORRo0aZbvu4Ycfjhx44IGR7du357r/okWLIikpKZFly5Zlu75Tp06RAQMG+N+7du0aad++fdzn27RpU6RMmTKR4cOHR6/T89SpUycyePBg//dXX33lX9cXX3wRvc/HH3/srwteR7t27SJXX311tmW3bds2um3XrFnj7//1119H8lOlSpXI0KFD870PAKD0obINAECIZs2a5dq1a5etuqnKsarVS5cu9X+rOtu1a1fXqFEjl56e7odzy+LFi6PL0PBtDX8OaJl7gp4noOHSElthDq5buXKl/6lqvZqtaYh5cFHFWFX5BQsWxH0ODaGOfS2iSrWu1za4/PLL3XvvvRcdav/LL7/4CvWBBx6Y7Xk0PD8Ybh9UtuPRfXbs2OG3e6BMmTJ+uLe2bV6vX5Xr2Neq+2r4eqzY/VC1alXfLE+vX1X1xx9/PNvc+YCG2//1119x1xUAUHrRIA0AgGKmxEzzo1944QU/V1mJ6iGHHOK2b99e7PtCSWggOEAQ77rYIe7//Oc/s82vDmj+dTzVq1ePzqUOaK635keridvnn3/urr76avfggw/6hFrPoXnoGkKvn7GUdEtYzd7ye60F8corr/htoXn4GlJ/2223+ddz1FFHRe+joes1atQIZX0BAHZQ2QYAIESal6y5u8Ec56CRlxps1a1b161Zs8YnmUrKVJnV/XMmorpOc4Bjm3H98MMPJWI/HXbYYW7mzJl+DnTOi7pvx6NGbytWrMj1OpUw68CDunWr0Zi2m6raur8q26ow53wONXsLKtKa7x6P5plrXbTdA6p0q0Ga5ngXlPZDzrnW8faD1nfAgAHu+++/9wdNYs8lriq79qPuAwDYu5BsAwBQBOvXr/dDmWMvS5Ys8RVa/bz22mvd7Nmz3QcffOAGDhzoG6KpEZq6ZqsD+fPPP+/mzp3ru3Trtlg6XZSqrBpercT2k08+8afzKgluvvlmn1SqgZles4bE6zXm1yBNiaaq27HJr4aiq3Hc9OnT3fz5891//vMfn3yr4q/h4926dfON2XRucA1PV3f2++67z3388cf+8UpulTxre+vAhLa1mtGtXr3adwa/6qqr3L/+9S9fcdY21LbUUG51FC+o66+/3r388su+ev3bb7/5/Thjxozo7VovrYcOEqgD+Weffea3R2wjOJ1jW0PldQAAALB3YRg5AABFoEpszmqlErkXX3zRJ8dK9Fq1auXn9ep6VbJFCbc6bGvosaqgTZs29ZVdncIqdqj0qFGj3JVXXumfQ9XYBx54wHcATzRVlDXUW128dfovVfCVSF5wwQV5PkZDwXWO7eHDh/su66LTZOm0WjrQoCq25onrNetAhCjBVQf3vn37+g7vStY1NDt4vBJyJbe33HKLn4utRF3zqzUXXrRsDQdXd/WNGzf6ruCffvqpP9hRUHpNqkyrs7mq09r+SuK1HFGHdSX56niuEQua833NNdf4YfaBN954wyf6AIC9T5K6pCV6JQAAQOmmYeQHH3ywPw2Xqtd7A1XBTzjhBF8Vz8jISPTqAACKGcPIAQDAHqe51ho2HnRc3xuoM/mwYcNItAFgL0VlGwAAAACAkFHZBgAAAAAgZCTbAAAAAACEjGQbAAAAAICQkWwDAAAAABAykm0AAAAAAEJGsg0AAAAAQMhItgEAAAAACBnJNgAAAAAAISPZBgAAAAAgZCTbAAAAAACE7P8BKek8x4j23pwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Data for plotting\n", + "labels = ['Kloppy', 'Fast-Forward', 'TDC (Rust)']\n", + "times = [kloppy_load_time, fff_load_time, tdc_load_time]\n", + "colors = ['#f87171', '#fbbf24', '#34d399'] # Red, Yellow, Green\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "bars = ax.barh(labels, times, color=colors)\n", + "\n", + "# Add titles and labels\n", + "ax.set_xlabel('Load Time (seconds)')\n", + "ax.set_title('Benchmark: Sportec XML Loading Performance')\n", + "ax.grid(axis='x', linestyle='--', alpha=0.7)\n", + "\n", + "# Add value labels to the end of each bar\n", + "for bar in bars:\n", + " width = bar.get_width()\n", + " ax.text(width + 0.2, bar.get_y() + bar.get_height()/2, \n", + " f'{width:.2f}s', ha='left', va='center', fontweight='bold')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmark: Polars Conversion\n", + "\n", + "Once the data is loaded into memory, we often want to convert it to a Polars DataFrame for analysis. Because TDC is built on Arrow, this conversion is **zero-copy** and near-instant." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- to_polars benchmark ---\n", + "TDC to_polars: 4.39ms\n", + "Fast-Forward tracking access: 0.01ms\n", + "Kloppy to_df(engine=\"polars\"): 1155.26ms\n", + "\n", + "Speedup TDC vs Kloppy: 263.2x\n" + ] + } + ], + "source": [ + "print(\"--- to_polars benchmark ---\")\n", + "\n", + "# 1. TDC to Polars (Zero-Copy)\n", + "start = time.perf_counter()\n", + "df_tdc = tdc_container.to_polars()\n", + "tdc_conv_time = time.perf_counter() - start\n", + "print(f\"TDC to_polars: {tdc_conv_time * 1000:.2f}ms\")\n", + "\n", + "# 2. Fast-Forward to Polars\n", + "start = time.perf_counter()\n", + "df_fff = fff_dataset.tracking \n", + "fff_conv_time = time.perf_counter() - start\n", + "print(f\"Fast-Forward tracking access: {fff_conv_time * 1000:.2f}ms\")\n", + "\n", + "# 3. Kloppy to Polars\n", + "start = time.perf_counter()\n", + "df_kloppy = kloppy_dataset.to_df(engine=\"polars\")\n", + "kloppy_conv_time = time.perf_counter() - start\n", + "print(f'Kloppy to_df(engine=\"polars\"): {kloppy_conv_time * 1000:.2f}ms')\n", + "print(f\"\\nSpeedup TDC vs Kloppy: {kloppy_conv_time / tdc_conv_time:.1f}x\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAEiCAYAAADklbFjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAATfJJREFUeJzt3Qd8FGX+x/EnCS2UhK70LkWK2CiiqKjYET2shygqKthObKiIDbAi6omg3oEFsGI9FZUmKlKU3qT3XhIQISSZ/+v7/G/2Zje72WzIEsJ83q/XEnZ3dnfm2d/OzO9pk+A4jmMAAAAAAECBSyz4twQAAAAAACTdAAAAAADEES3dAAAAAADECUk3AAAAAABxQtINAAAAAECckHQDAAAAABAnJN0AAAAAAMQJSTcAAAAAAHFC0g0AAAAAQJyQdAMAwpo8ebJJSEgwH3/8cZEooccff9yu7/bt282RSuun9cSRGz9Hu5kzZ5r27dubMmXK2O2dM2dOYa8SABz1SLoBoBCMGjXKnvB6b1WrVjVnnXWW+eabb/hOjhA33HBD0HeUkpJiWrVqZV588UVz4MABczRYsWKFufXWW039+vVNqVKl7Daedtpp5uWXXzZ//fVXYa+eL3hjLDEx0VSvXt2cd955tuKrIB08eNB069bN7Ny507z00kvm3XffNXXq1CnQzwAA5FQszGMAgMPkySefNPXq1TOO45gtW7bYZPzCCy80X375pbn44ov5Ho4AJUuWNG+99Zb9/+7du80nn3xi7rvvPtti+P7775ui7D//+Y9NwrSN119/vWnevLnJyMgwP/30k7n//vvNwoULzRtvvGH84NFHHzUPPfRQoX3+ueeea78D7QtWrVplhg0bZs4++2z7HV1wwQUFVsGyZs0a8+abb5qbb765QN4TABAdSTcAFCKdTJ988smB+zfddJM55phjzNixY0m68+jPP/+0XWXjpVixYubvf/974H7v3r1NmzZtzAcffGCGDBliWyWL4nYrsbv66qttS+fEiRNNtWrVAs/16dPHLF++3CZ8Rdn+/ftNiRIlbOtxXr5n3QrLcccdFxRnXbt2NS1btjRDhw495KTbjZWtW7fa++XLlz/k9Q19bwBAZHQvB4AjiE6Gk5OTc5z8Z2dn25Pv448/3nYBVmKuLsG7du0KWq5u3bo2WVdL5amnnmqXVbfhd955J8dnqdX2H//4h32NWjpr1qxpW9pCx0TrswcOHGif1/t16tTJJmReZ555pm0lnTdvnunYsaMpXbq0adiwYWA8+JQpU2yiqm1r3Lix+eGHH4Jer9Y3JbN6TstUqlTJtsCuXr06bLd8vZ+WV5d8rVckel+th9ZNPQlk3759ZsmSJfke+60ETtsr7vopmXErTFRG6oL+9ttvR32vgtjuPXv2mHvuuSfwPeo5tZr+/vvvuX72c889Z/bu3Wv+9a9/BSXcLpXb3XffHbifmZlpnnrqKdOgQQP7Ofq8hx9+OEc3+7zE4KxZs+z2hCuj8ePH2+e++uqrwGMbNmwwPXv2tOWrz9bv4N///nfYOQjU+0Ct1jVq1LBxmJ6ebrtVP/HEE6ZRo0Z2fVTOHTp0MN9//32uY7oLcptj1aJFC1O5cmVbOeJS3P7tb38zFStWtJ+hCrsvvvgiT7GioRL6bYpiTMu4cSyqeDn99NNtAq39UJcuXczixYuD3tsto0WLFplrr73WVKhQwZajtwz0PWi9FM/aBreL/Lhx4+x9rfdJJ51kZs+eHfTe2ndoHd1hDscee6z9znfs2BF2HbQP0vJa19TUVHPjjTfa33ao9957z34nigWt7xlnnGG+++67oGU0pMfd9nLlypmLLrrI9vIAgIJCSzcAFKK0tDSb/KlLqRK3V1991SZC3hYvUYKtk2mdWN511132RPyf//ynPXH9+eefTfHixQPL6mRUJ+ZKAnv06GGTE52c6kRXyYroM3SSqZNqndieeOKJdj10Ar9+/Xp7su965plnbKKpLtVaXyVr1113nZk+fXrQOqoCQCfdaj3VSf3rr79u/z969GibFN522232RP3555+367du3Tp7givqqv3LL7/Y5ZUgKOnU65UU6ARfJ8xeSiaqVKliHnvsMdvSFqkrrbrnKkFRcuVu04wZM+zY+QEDBuR7UjO9tyh507hnrafK/Y477rDDBT766CNb5qrY8CauoQpiu1WuqtzQZzdr1swmKUr+9N3qe41EQxiU4GhSrbxQd2Qlyfru+vbta7//wYMH28/59NNPg5aNFoNKyvTZH374oX3eSz0IlBx17tzZ3ldlSdu2bW2ipW3U9itJ0nsroVZseSlJVuu24lXJsf6v71nrqm1QAqbXKfFXxYQqKA7HNsdKvyfdVPkhSgI11l6VCeoGrwRR5XfZZZfZIQ9qGc8tVpRs6rWDBg2y+5BTTjnFVmKIKsHUmq7vRGWlmNa+SJ+nMlJC7aXftyow9F7ad3nLQL9x7a+0D3vhhRfMJZdcYoYPH24rK7ROojK88sorzdKlSwO9EPQbXblypd3HKeF2hzbo76+//pqjQkSv129N76V11BAQVTA8++yzgWVU0aLtUYxrKI9iQd+hKhg0Zl40rl3fl+JNr1Xirt+gKhO0fw3ddgDIFwcAcNiNHDlSZ6o5biVLlnRGjRoVtOzUqVPtc6NHjw56/Ntvv83xeJ06dexjP/74Y+CxrVu32vft27dv4LHHHnvMLjdu3Lgc65adnW3/Tpo0yS7TtGlT58CBA4HnX375Zfv4/PnzA4917NjRPjZmzJjAY0uWLLGPJSYmOr/++mvg8fHjx9vHVQauffv25ViPadOm2eXeeeedHOXWoUMHJzMzM2j5AQMG2Oe2bdvmLF682KlevbpzyimnODt37gxazt0uLR9Njx49nDJlytj31G358uXOoEGDnISEBKdly5Z2maFDh9r3e++99wKvy8jIcNq1a+eULVvWSU9PDzwe+rkFsd2pqalOnz59nFikpaXZ9+vSpUuelp8zZ45d/uabbw56/L777rOPT5w4MeYY7Nevn1O8ePGg70dxVr58eadnz56Bx2666SanWrVqzvbt24M+++qrr7bb7pah+73Wr18/R7m2atXKueiii3LdRjd+4rnNkei12k7FmF43ffp0p1OnTvbxF1980S6j+y1atHD2798f9Ftt376906hRozzFiltGH330UdDjJ5xwglO1alVnx44dgcfmzp1rf7vXX399jjK65pprcmyDWwa//PJLjt96cnKys2bNmsDjI0aMsI9rfXL7LYwdOzZHubrr4I0R6dq1q1OpUqXA/WXLltn11+NZWVlh93F79uyx8XbLLbcEPb9582YbW6GPA0B+0b0cAArRa6+9Zlt4dFM3SLXAqnVNXTFdajVV90m1yKk12r2pBa1s2bJm0qRJQe+p1k61YrvU2qXuy2pFcqllTF2gQ1vHJLRFSS1PaiFyue/tfT/RuqjF1qXPVNfPpk2b2q7lLvf/3terK6pLXYHVWqsWPr0+XDfpW265xSQlJYUt0wULFthutGqhUgueWk291IqsPCevrdxqJVQZ6qZ1Uotdu3btAi2dX3/9tW2Zu+aaawKvUc8DtSaqR4G6+UZSENutZdV6t3HjRpNXaukVt6dBNNpGuffee4MeV+uvhI79zksMXnXVVXabvbGubr/qHaDnRN+TYlWtpfq/N/7VMqmeF6HlpFZLb7m6ZaQW02XLluVpe+O1zblRN3+9Rq21+o2oB4s+Wy35mm1crbNq3dVwArcMFC8qB22XuuDn9TfitWnTJnvZMLXKq1eIS+PJtc9xy8FLvSvCURnotxH6W1ePk9q1a+d5H6Cx+No+9XCQcL+F0HVQ2as83Nj+7LPP7NAYtfSHjul393Ha7yre9Nv1xpbKTesYum8FgPyiezkAFCJ1dfVOpKaTv9atW9tutOqqrWRXJ9RKLnQyHo47OZLLe3LrUuLpHf+t7tFXXHFFntYx9P3cJDZ0PLm6R4cm7KosqFWrVo7HQl+v7qzqJjpy5EibPHi7rGrbQ6lbaSRK0NRtVmODVRFwqDS+VF2xReN69dneceQal62utqEn9qpscJ+PpCC2W939lWiqnFURo9nvNTZfXYUj0WXBRAlcXmgbtH1uV2eXKhuU0IZuY15iUJU+TZo0sd3J1SVb9H8NA1CSJtu2bbNJkboZR5pFPTT+w5WRuhZrjLImK9P4/vPPP990797dJpaHc5tzo/XT716/IVWGqEu6O0GZum0rNvr3729vkcpB3cfz8hsJ3U5RBUEoxbB+R6GTpUV679AycH/redkHqGJB3cE1Jj/0Ow33W8htv6T41j5O358qAiJxK2HceIv0OwGAQ0XSDQBHEJ0kqrVb10jWCaFOvNVao4RbY6PDUeuYV6TWLW9CF4u8vl+k5fLy+jvvvNMmnmrVU0uZTsqVfKjlXNsfKrQl00uVCRqHq/LS2NJDpfU/55xzTDwUxHar9VOtfGp5V0uxxsxrbKpakCPNeq1kQrOuq1dALEIrVQ41ZtSirUn61LqoRFNzCqjiyZ1I0C0DjQ8OHfvtCk2cw5WRxjMrCfv8889tGWn8r65TrbHG0S6dVdDbHIkqciLFmVsOGqfujnUPFVo5kNtv5FBFeu9D2QcojjW/gS5Vd8IJJ9gKM223KkjC/RYKYj/nvq/GdasyJVRhzmYP4OjC3gQAjjCaMVnUNVk0c7K6SWtSo4I6kdZ7xppwxZMmAlNS9eKLLwZ1MVUrZ6yUdOpkWZM2KZHTxE7xpEtuaeZlncB7W7s107T7fLy3W7OPa3t1UyuhJlBTMpvbpabUk0Ktx9OmTQvqEhxpG7V9qghyW/DdSc60rrltY26UdKt1U13I1TtBXYO9QxRUoaTvMCsr65ArPtR1WkMldNNvS4m4hhhESrrjtc354fZa0LCFgq4AcrdDk5qFUgyr50G8Lwmm1ukJEybYWFB3cFcswwHC7eP0/WlCQiXxkZYRVWrGq2INAIQx3QBwBNEYV7XEqVu5e6KvFiAlHZqVOVyCnp/EVK3Bc+fOzTED86G0iB8KtVqFfq5mT9Z2x0otk0omNZO0EtrQSyod6iXDQqk79+bNm23XaO/3ovVXa517maZ4bLeWC+16qwRCrdihl7UK9cADD9hkSkmnezk1L7UMq8eFu42iy9Z56Trlokss5YdiXJeRUtnppsoDJcPe8lGsKikPV0mk7ud5EXrZKX0vahnOrYzitc35oe9UcxGMGDHCjsHObzmEozJXUqreId59icpb+yK3HOLJbbUO/S2Eln0sNKu7KsE0tCC0pdz9HPUaUK8PzcKufW9BlisAeNHSDQCFSJc+cltE1UI5ZswY27qjSwK54wmVtKmbtMb+asIjXepGLV5aTpOsKTFSghkLdeFUK6su/aNLhmkssMZUKkFVl1uNtz2c1OqqLp7qXq0xmGp9Veu+LsmVHzrZ1sR0OvFWpYUmg3LHbRbEJcO8evXqZZMhTUT122+/2QncVLaaCEtJQ26TlR3qdmtMtrol6/vXd6ZkUq/Xpci8reeRWvkUb2ptVvKrceAa75yRkWG7+bqXPRO9tyowVJmhxEwxqXJUoqYyVnnmlz5frZsaO6+x3aFj43XJOk1opYmtNDmYykmxqsm1tK36fzR6jZJWxblavHW5MPcya5HEc5vzO+miLmOlSgqVg1q/VVmimNFl/lSJll/qHaJeEerxoO/AvWSY4rIgfiPRaF+nyhbNT6DkV2PTlfB7r1EeK1WqPPLII7ayUsMvLr/8cjsng34bqpTS/lSfq8uDaXy/eoeol4V6V6xdu9ZOlKfeRbo0IwAcKpJuAChE3q6USjo0sZROAkPHIisRVsKg5E6zZ6v7tJI7jXXViWGslJxNnTrVJp5q7VYioda0Tp06BU0Sdrio4kCtXRqHre7V2iYlVJHGr+aFKiaUWCmZ0CRVej/vLOoFRV3+J0+ebCtKVI7qIq1JqTRW201a47Xduo63upQrQdEYbrXoKdkYNmyYuf3226O+/tJLL7Vd45V0abyzYk+JicZJK2lXcufSOGglerpevGJGY2D79etnY+hQKOl+9NFHbQ8Ed9ZyL3U7V7KrFktto7ZNlRKa78B7TebcaCZ5VSipnNS6rS7VTz/9tK18yk28tjk/VHGgygJ1wdb6qPVev1lNvOjdj+SHulZ/++23drv0XvrtqJJB5ZvXCdkOlSqANMeBKhfUEq3KRVVKKkHOL8WM1l8VCErA9XtRbCvJdmn4iT5DlTv6HSg+lPQrUddQBAAoCAm6bliBvBMAAAAAAAjCmG4AAAAAAOKEpBsAAAAAgDgh6QYAAAAAIE5IugEAAAAAiBOSbgAAAAAA4oSkGwAAAACAOOE63YhI13vduHGjKVeunElISKCkAAAAAOSL4zhmz549pnr16iYx0V9tvyTdiEgJd61atSghAAAAAAVi3bp1pmbNmr4qTZJuRKQWblm9erWpUKECJeVjWVlZZtGiRaZZs2YmKSmpsFcHhYQ4ALEA9gng+ID8niekp6fbBj03x/ATkm5E5HYp1w8jJSWFkvKxzMxMu/NULBQrxm7Dr4gDEAtgnwCODzjU84QEHw5b9VdnegAAAAAADiOSbgAAAAAA4oSkG1ExhheKgSZNmhALPkccgFgA+wRwfADnCbFjcCai8uO4C+SMgfLly1MsPkccgFgA+wRwfADnCbGjpRt5mhQB/qYYmDlzJrHgc8QBiAWwTwDHB3CeEDuSbgB5vgwEQByAfQI4NoDzBHC+GBuSbgAAAAAA4oSkG1F1XvKuOWXBG5QUAAAAAMSIpBtRZTGPmu9p1uqWLVsye7nPEQcgFsA+ARwfwHlC7Ei6AeRJiRIlKCkQB2CfAI4N4DwBnC/GiKQbUSU5FJLfafKsWbNmMYmWzxEHIBbAPgEcH8B5QuxIugEAAAAAiBOSbgAAAAAA4oSkGwAAAACAOElwHIcRuwgrPT3dpKammta/DDVJ5ZLNzOa9KCmf0m5C43k1e3VCAtPZ+xVxAGIB7BPA8QH5PU9I/29ukZaWZlJSUnxVkLR0A8iTjIwMSgrEAdgngGMDOE8A54sxIulGVMxeDtVazps3j9nLfY44ALEA9gng+ADOE2JH0g0AAAAAQJyQdAMAAAAAECck3QDyRJNiAMQB2CeAYwM4TwDni7Fh9nJEn7182lCTVJbZywEAAADkTzqzlwO51cxQOn6nS0Ds3r3b/oV/EQcgFsA+ARwfwHlC7OhejuhBQp7le5q1esmSJcxe7nPEAYgFsE8AxwdwnhA7km4AAAAAAOKEpBsAAAAAgDgh6UZU9C5HQkKCSU5Otn/hX8QBiAWwTwDHB3CeEDtmL0dEzF4OAAAAoCCkM3s5EBmzlyM7O9ts3brV/oV/EQcgFsA+ARwfwHlC7OhejuhBQv9y31OytXLlSpJunyMOQCyAfQI4PoDzhNiRdAMAAAAAECck3QAAAAAAxAlJN6Kidzk0a3Vqaiqzl/sccQBiAewTwPEBnCfEjtnLERGzlwMAAAAoCOnMXg5ExkRq0ARa69evZyI1nyMOQCyAfQI4PoDzhNjRvRxRcckwkGxBiAO4iAUQB/BinwDiIHck3QAAAAAAxAlJNwAAAAAAcULSjaicBArJ7xITE02VKlXsX/gXcQBiAewTwPEBnCfErlg+XgOfyU4wJqmwVwKFnmw1aNCAb8HniAMQC2CfAI4P4DwhdjRbIXqQcKFu39MEKStWrGD2cp8jDkAsgH0COD6A84TYkXQjKmYvh5Ktbdu2kXT7HHEAYgHsE8DxAZwnxI6kGwAAAACAOCmySXf37t3NoEGDCns1ioxvv/3WnHDCCXFtqbzyyitNQkKCvV199dW5Ljt06FDTqlUrU758eVOyZElTs2ZN061bNzNv3ryg5ebPn2+uuOIKU6NGDVOqVCnTsmVLM3LkyLhtAwAAAAAUStLtJlORbo8//rhZvXp10GPlypUzxx9/vOnTp49ZtmxZjvfMyMgwzz33nE2+SpcubSpXrmxOO+00m1QdPHgw4rrMnTvXfP311+auu+6y90eNGpXrut14442msGzevNnceeedpn79+ja5rFWrlrnkkkvMhAkTDut6nH/++aZ48eJm9OjRcZm9XN/ZRx99lOf3nDJliu2urHLRBF2bNm0yH3/8sTnrrLPMn3/+aZdZtGiRadu2rRk3bpzZv3+/adSokU3Ce/bsaZN2HN4JtFQxwuzl/kYcgFgA+wRwfADnCXFMupUUuTclPCkpKUGP3XfffYFlf/jhB/uYkmO1Ri9evNgm1t5EUwl3586dzTPPPGN69eplfvnlFzNjxgyboL/66qtm4cKFEddFz6tVtGzZsvb+VVddFbQu7q1///6mRIkS5pZbbslH0fxvPfNLlRAnnXSSmThxonn++edtwqgWZyWW2s7D7YYbbjCvvPJKvmYvz40m2FIFSLt27Wxilhdjx441GzduNL///rtNrh9++GH7+M6dO82SJUsClSn79u2zlRWqtFH5ucupkuevv/4KbJcqV+rWrWvefvttW7Gh+LznnnvsMvqbmppqateubYYPHx5Yh6ysLNOvXz+b+KsVvWLFiubkk0+23xWCkWyBOAD7BITi2ABiAewT8sjJh5EjRzqpqak5Hl+1apXmuXZmz54d9HhWVpZz5plnOnXq1HEyMzPtY88++6yTmJjo/P777zneJyMjw9m7d2/Yz9br9dlfffVVrus4efJkp1ixYs6bb74Z9PjUqVOdDh06OKVKlXJq1qzp3HnnnUGfpXV88sknne7duzvlypVzevToYR//+OOPnWbNmjklSpSwy7zwwgtONBdccIFTo0aNsNuya9euwP/XrFnjXHrppU6ZMmXsZ3br1s3ZvHlz4PkBAwY4rVq1coYPH27XOTk52S6ze/du+/yUKVPstm7atCnoM+6++267rd7P0fezfPlyJy/S0tLs8if9PNQ5ef6IsMscPHjQadOmjZOSkuKsXLnSlo1ec9VVV0V9/3HjxtnXNm3a1MaCXlelShUnPT3dPt+3b1/7WMmSJZ2dO3faxx599FH7mG7abtF3pPv6bkqXLu00aNAgsIzeW+tWq1Yte1+fs2jRIvu6l19+2T6WlJTktGzZ0mnYsKF9j44dO+apfPxEvzuVm/v7hT8RByAWwD4BHB+Q3/OEtP/mFvrrN4mHqyb07rvvNmvWrDG//fabfUzdnM855xzTunXrHMurG3SZMmXCvpfG/KalpdkWyUj0OWoJv/XWW83NN98c1CKrbtYaI6z3+eCDD8xPP/1k7rjjjqDXv/DCC7Zlfvbs2ba1XOus8coap6zWVrWy6nG1xEaiFlu1aqtFO9y2aCyzaIx1ly5d7PLqcv3999+blStX2tZ7r+XLl5sPP/zQfPnll/Z9tW69e/e2z51xxhm2tfbdd98NLK/u+SpjdcV2qaX3mGOOMVOnTjWxyK2h+4knnjDTp083w4YNM/Xq1Yvpfbds2WJfq54QKge9ftKkSXZYglx++eUmKSnJHDhwwHYt13jugQMHBl6/YcOGHL0SvvvuO/PHH3/Y1m633NTjQq3pas3W56icxR3yoOEHWkb3d+zYQUt3GI7j2N+d/sK/iAMQC2CfAI4P4DzhCJ5IrUmTJoEu16IEx30sFkqolYhVrVo17PPqjnzZZZfZseSh434HDx5srrvuOtvdWElc+/btbXfrd955x44Zdp199tmmb9++dqyxbkOGDDGdOnWyifZxxx1nuzMrUc+tG7KSPZ2gRttGdblXIj9mzBjbFb1NmzZ2fZQYzpw5M7Cc1k+PazI0JdnqYv/+++/bMeNy0003BU0wpuRcr1FlgVf16tVtGYaj5DY9PT3olptZs2bZMv373/9uyzVWt912m02CtT6qZFi1apX9u2fPHvu8vp/PP//clonWTQnx9ddfH1Q541WhQgU7J4AqeVTBIM2bN7fdzjUUwY0ZJfty8cUX227pb731lp2oTd3+n376advNHAAAAACKVNLttpApyfHej5XG6GqMr/s+oZR87t69207qVaxYsaDn1Jqp1mklYO5N48qV+Cnhc4W2oqslVsmcl+6r4kDjgtVy7H1PtTDndfv03mqVdVtmpVmzZrYlXM+5lEQqMXRp/LTWe+nSpfa+KgKU6P/666/2vrZTCXdoK3tycrKtmAhHCbTGPrs37zqFs2DBArv9mgDN3fa1a9fa5z755BN7X62judH3qG1zx2prLL/Ge7suuugiu01KxNWyre/L1bhx46D30jhul/vdex8LjT29l8aU67PV40It5M8++6z9bvfu3ZvregMAAABAXgRnpXHkJpBuF2S1GLsTZsVCM5wraVRXYk2S5qWESS28P//8s10ulBIpdTl3Zz33cltGJVLX9kiUpM+ZMydwX1241b1bSV5+tjE/1IqrWdHV2q0y/uabb8zkyZNzLKdu7FWqVAn7HppU7N577w3cV0u3Em9NpJZb7Yy3l4ArMzPT3pTgqpeAEuauXbvaxF4t1pp9Xq3a7neo+y539nJRi3/Hjh3t/9etW2e79ot6MqgV+1BoiIHKwu2yrl4D1apVsy3hqsxQzwP8P/Ue0BAGZi/3N+IAxALYJ4DjAzhPOEJbutUiq27cSgbdMdzXXnutneVcY5NDKWH1Jl5e6l4tGqPrpSTzkUcesUmnxmOHc+KJJ9rXNWzYMMctNIH3atq0qU3kvXRfFQfq6q7WY+97aUyyuiirJfW1114Luy1qjXffW8mkbi6to55Xi7dLLcia7dul1l+dAHtbezV+XePU33jjDdstPrR1XsmxxrWHG0cv6kGglmHvLbdLhql1XUm191anTh37nBJq3VeLvT5TSaxmlBe1WqubuJ5r0aKFrfBQwi8qO43l9rZ0q0JBy2lIgFqjdXm5N998M2Jvh7zSGHlVKujzlWDrM0Tvr/LD/yjW9D2QdPsbcQBiAewTwPEBnCccIUm3WjLVaqgJwb744gs7YZouB/avf/3LJqmicdVKCtUKqsRUXb+1vBIhXZs53HW9RS2TSp41AZpLyyqJV9J5+umn28/23tS6Kw8++KC9NJnGY6tlWq/TmOHQidRCaXy3xl4/9dRTNunTZan++c9/Bl0mLRxtl7pfn3rqqba7tT5PLf6qgFD3cFHZKNnTmGh1dVY5KSFV6663m7smAevRo4ctJ3VnV2u9uo8fe+yxgWWU5CtR1rjkcNcmV6KuxNr97LxKzDYFSsm2JqVTq7ISciXjSn41NlwTq7mJu6j1Xl3FlbSrB4IS8mnTpsW8DeFobLwm1lOlkLrKq5JA4/lVgeNOdIf/pzhW7Okv/Is4ALEA9gng+ADOE/IhHpcMc2+6fJMu2dS7d29n2bJlOZbfv3+/M3jwYKdFixb2El4VK1Z0TjvtNGfUqFH2UlSRDBs2zGnbtm3g/uOPPx70uaE37yWgZsyY4Zx77rlO2bJl7SW6dKmogQMHBp7XJa9eeumlHJ/pXjKsePHiTu3atZ3nn38+T2W1ceNGp0+fPvZ9dTkqXUJMlwebNGlSzJcM03ZXr17dltXf/va3wGW0vPr3728vgaXPDdWrVy/n1ltvdfLKndb/5FwuGQZ/0O9x2rRpuf4ucfQjDkAsgH0COD4gv+cJaT6+ZFiC/jFFjCZTU7dqdaUuiBbPI53GMX/22WdB48Yj0URy27Ztsz0MvLZv327LTDOO5/XSXhrTrQnVTv55qDEpyWZm81753gYUbRqfr9hR74vQCQrhH8QBiAWwTwDHB+T3PCH9v7mFJlr2TnbsB0Xy7FljqHX5LCWS+H8KXvfSY6EJt3uptvxcSxsAAAAA4LOkW84888zCXoUjSpcuXex4cF37+txzz83xvGqcQi+FllfRZi/H0U9zMeia8+6cDPAn4gDEAtgngOMDOE+IXZHsXo7Dw+0C0nraUJNUlu7lAAAAAA4tt0jzYfdyGjARVVIBz16OojlGZ+bMmfYv/Is4ALEA9gng+ADOE2JH0g0gT7hcGIgDsE8AxwZwngDOF2NH0g0AAAAAQJyQdAMAAAAAECck3YgqK4FC8jvNWt2yZUtmL/c54gDEAtgngOMDOE+IHUk3gDwpUaIEJQXiAOwTwLEBnCeA88UYkXQjqiQuKud7mkRt1qxZTKbmc8QBiAWwTwDHB3CeEDuSbgAAAAAA4oSkGwAAAACAOCHpBgAAAAAgThIcx2HELsJKT083qamppvUvQ01SuWQzs3kvSsqntJvQeF7NXp2QwHT2fkUcgFgA+wRwfEB+zxPS/5tbpKWlmZSUFF8VJC3dAPIkIyODkgJxAPYJ4NgAzhPA+WKMSLoRFbOXQ7WW8+bNY/ZynyMOQCyAfQI4PoDzhNiRdAMAAAAAECck3QAAAAAAxAlJN4A80aQYAHEA9gng2ADOE8D5YmyYvRzRZy+fNtQklWX2cgAAAAD5k87s5UBuNTOUjt/pEhC7d++2f+FfxAGIBbBPAMcHcJ4QO7qXI3qQkGf5nmatXrJkCbOX+xxxAGIB7BPA8QGcJ8SOpBsAAAAAgDgh6QYAAAAAIE5IuhEVvcuRkJBgkpOT7V/4F3EAYgHsE8DxAZwnxI7ZyxERs5cDAAAAKAjpzF4ORMbs5cjOzjZbt261f+FfxAGIBbBPAMcHcJ4QO7qXI3qQ0L/c95RsrVy5kqTb54gDEAtgnwCOD+A8IXYk3QAAAAAAxAlJNwAAAAAAcULSjajoXQ7NWp2amsrs5T5HHIBYAPsEcHwA5wmxY/ZyRMTs5QAAAAAKQjqzlwORMZEaNIHW+vXrmUjN54gDEAtgnwCOD+A8IXZ0L0dUXDIMJFsQ4gAuYgHEAbzYJ4A4yB1JNwAAAAAAcVIsXm+Mo8f4Jt1NxYoVC3s1AAAAAKDIoaUb0YMkkTDxO8VAlSpViAWfIw5ALIB9Ajg+gPOE2DF7OSLy8wyDAAAAAApOuo9zC5owkafJMeBvioEVK1YQCz5HHIBYAPsEcHwA5wmxI+lGVCTdUAxs27aNWPA54gDEAtgngOMDOE+IHUk3AAAAAABxQtINAAAAAECckHQjepAwe7nvKQZq1qxJLPgccQBiAewTwPEBnCfEjtnLEZGfZxgEAAAAUHDSfZxb0NKNqLKysigln1MMLF68mFjwOeIAxALYJ4DjAzhPiB1JN6JyHIdS8jnFgGoliQV/Iw5ALIB9Ajg+gPOE2JF0AwAAAAAQJyTdAAAAAADECUk3ogcJs5f7nmKgfv36xILPEQcgFsA+ARwfwHlC7Ji9HBH5eYZBAAAAAAUn3ce5BS3diIrZy6EYmDt3LrHgc8QBiAWwTwDHB3CeEDuSbkTFjNVQDPz111/Egs8RByAWwD4BHB/AeULsSLoBAAAAAIgTkm4AAAAAAOKEpBtRJSUlUUo+pxho0qQJseBzxAGIBbBPAMcHcJ4Qu2L5eA18JiEhobBXAUdADJQvX76wVwOFjDgAsQD2CeD4AM4TYkfSjaj2PfKIKVm8OCXlY1mJiWZ+48amxdKlJik7u7BXB4WEOACxAPYJ4PhQdJQcMuSwfl5mZqaZPXu2ad26tSlWjDTTi+7lAPIki2EGIA7APgEcG8B5AnI7X8zKonzCIOkGAAAAACBOSLoBAAAAAIgTkm5EDxLG8PqeYqDZsmXEgs8RByAWwD4BHB+Q21VOWrZsydVuwiDpBpAnJQ4epKRAHIB9Ajg2gPMERD5fLFGC0gmDpBtRZScSJn6nGJjTrBmx4HPEAYgFsE8AxwfkNonarFmzmEwtDLIpAAAAAADihKQbAAAAAIA4IekGAAAAACBOEhzHceL15ija0tPTTWpqqtnSu7dJLVmysFcHhcj573hezV6dwDfhW8QBiAWwTwDHh6Kj5JAhh/XzlFZqXLdmMU9ISIiYW6SlpZmUlBTjJ7R0A8iTjOLFKSkQB2CfAI4N4DwBkc8XMzIonTBIuhEVs5dDMbCoUSNiweeIAxALYJ8Ajg+IRK3c8+bNY/byMEi6AQAAAACIE5JuAAAAAADihKQbQJ4kZWVRUiAOwD4BHBvAeQIiny8mJVE6YTB7OSJyZxjc2ru3SWH2cgAAAKDIONyzl0eTzuzlQGRcUw6KgbSyZYkFnyMOQCyAfQI4PiDieYLjmN27d9u/CEb3ckTF7OVQDCyvW5dY8DniAMQC2CeA4wNym718yZIlzF4eBkk3AAAAAABxQtINAAAAAECckHQjOsZlwHFMqf37iQW/Iw5ALIB9Ajg+IIKEhASTnJxs/yJYsZD7QA5JJN2+pxg4fvly35eD3xEHIBbAPgEcHxDxPCEpybRq1YoCCoOWbkSVTRn5nmJge4UKxILPEQcgFsA+ARwfEPE8ITvbbN261f5FMJJuROUkEiZ+pxhYU6MGseBzxAGIBbBPAMcHRKJke+XKlSTdYZBNAQAAAAAQJyTdAAAAAADECUk3omMiNTiOSdm7l1jwO+IAxALYJ4DjAyLQrOWpqanMXh4Gs5cjKmYvh2Kg0erVFITPEQcgFsA+ARwfEPE8ISnJNG3alAIKg5ZuRJXNtfZ8TzGwsWpVYsHniAMQC2CfAI4PiHiekJ1t1q9fz0RqYZB0IyqHpNv3FAObqlYlFnyOOACxAPYJ4PiASEi6IyPpBgAAAAAgTki6AQAAAACIE5JuRJXA7OW+pxiotGsXseBzxAGIBbBPAMcHRJKYmGiqVKli/yIYs5cjqkSSbt9TDNTdsMH35eB3xAGIBbBPAMcHRDxPSEw0DRo0oIDCoBoCUTF7ORQDq2vUIBZ8jjgAsQD2CeD4ULT9+OOP5sILL7Qt0rqutm7Dhw8PWmbEiBGmQ4cOpkyZMoFllixZkuO93OdCb4888khgmcmTJwce1zW8xb2W9w8//JDrun755ZfmsssuM3Xr1jXJycnmmGOOMeedd56ZMmVKjmW3bdtm7rzzTlOnTh1TokQJU7lyZdOpUyezcuVKcyQg6T4MzjzzTHPPPfeYoorZy6EY2FGhArHgc8QBiAWwTwDHh6Lt999/N99//72pWLFixGW++eYbM3v2bJuY58UJJ5xg2rRpY0499VRz/PHHmxo1auRYpkSJEubkk0+2/9dfLe8m4ZF88skn5vPPPzdZWVmmYcOGNrHWuiuZnjZtWmC57du32/f75z//aTZv3myOO+44m6BrmY0bN5ojAUl3AbjhhhtsLYzXxx9/bEqVKmVefPHFgvgIAAAAADgk3bt3N+np6Wb8+PERlxk2bJhd5vHHH8/Te3766afm119/NT///LN56623TK9evXIsU61aNTNhwgT7f/3V8qecckqu73v66aeb6dOnm3Xr1pn58+fbzxEl4e+//35guUcffdSsWrXKJvyrV682CxYsMAsXLjS7d+8OfIa3xV3reMYZZ9jW8/bt25sVK1bY5F7JuioCrr76arv9rq+//tq0a9fOlC9f3pQuXdpWAFx11VVm165dJq9IuuNAX+R1111nXn/9ddO3b994fAQAAAAAxKRSpUo22cxN9erVTVJSUp7fUy3XSkZbtWpl3nnnHXPgwIEcy2zcuNHUrl3b/l8t1WqgjOamm26yrefeJNxVsmRJ+9dxHPPhhx/a/9eqVcuce+65tlu81kUt5e5yXnfccYfZsmWLva64WsPPP/98m0Rrm/fs2WM++OADM3jwYLusWte7du1qKwmUkDdq1Mjs2LHDfmZaWlqey4iku4A999xzdjyBal9uvPHGsMuoVuT66683FSpUsAF6wQUXmGXLlgWeHzVqlK1J+eyzz+wXqxbzzp0721oel2qe1JVDYy4UYHqfK6+8MvDla7xG8eLFbRcLL3Vz9wZsXjB7ORQD1bZuJRZ8jjgAsQD2CeD4AC/lMzVr1rTJ7aJFi2yjY7gcqGrVqjZnkVmzZplu3brZZWOhFnjRZymXcpNit8X522+/ta3bWqd58+aZa6+9Nmxyr9b+pUuXmvvvv9/eX758uenfv79ZvHixbTiVSZMm2b9r1641GRkZply5cnZc+9y5c83OnTvNjBkz8tz9Xki6C9CDDz5onnrqKfPVV1/ZGpHcuqMr2L744gtbu6IaGk1ocPDgwcAy+/btMwMHDrS1ReqqoQBSVwcvBYhqWTTJgIJMYy969+5tn1OXifr165t33303sLzef/To0aZnz55h10u1UupK4b0Js5dDMVB961ZiweeIAxALYJ8Ajg9wqfVXrb5z5swxGzZsMGeffbZ9/KOPPgo0FqrL9/Lly8369ettTuMm4BLLMNwnn3zSJsZqVFR+1Lx5c/t4ZmZmYJmmTZvaidN00/9F47xDXXLJJfavJmgLfUz5k6gl3F1/PaYWcK33iSeeaHO5TZs22Rb1vCLpLiCacECt3BoPoC4TkahFW8m2uqCrxVldH5QIK1DVsu1NkBUkGj9w0kknmbffftv88ssvtlbFtX//fht0avFWkv3qq6/aFna3dVtdMkaOHBlYXsm5XqMW8XDUjULdJtybWxuVlZBQIGWEoksxsKxuXWLB54gDEAtgnwCOD3Bp8jKNkRb1uu3SpUvgOTfpVmtwg5DLiLVt2zbQihyNciK1nA8YMMCULVvW5lreXEbvr0naRHmV/q+b/i8a4x0qJSXF/i1WrFiOx9ztUaOoqMfxb7/9Zl555ZXA9qlRU/9X5UJekXQXkJYtW9raEgXE3r17Iy6nbgv6ghWk3rEVjRs3ts+5tIx3coEmTZrYLufeZTQuwjs7oBJ0jU1QdwlRLYxqllQL5XZbV5BGqpXp16+f7Z7u3gLd2Um6kZBg0suWJRb8jjgAsQD2CeD4gP8OZVXXbU1qJmrYU8OiS5fuEjUQTp8+PajM3NzE29KsPET5jrfxUvmIhuEqh1HOM3XqVHvfSy3fanwUdSlXkq6b/i8aqnso1PNX+ZfGgb/33nt29nddtswtg7wi6S4gCgTNiqcWaw3GVxeEwqYuEOoqodZudZFQa3ykruXu+AjV8nhvAAAAAI4O48aNs7Nv65LGrscee8w+5o5n1pBZ3ddfl+aX0mNq8RV14da4bPWOVeOjJl9zZyfv0aNHoGFw4sSJtmVbLdKnnXaafWzr1q32r/d63uqurYZDzSTueuCBBwLvqTzltttus++lmzukVp5++mnbuq0x5fXq1bM3/V8Toz388MOHVF5aV81wrkZSbacqBtyZ33U/r0i6C5BqdHSxdnXvjpR4a3yBxh54a3w0FkJB1qxZs8BjWkbjvl16XuO63fEJbpcM77XnVGuUmJhoW81dN998s52B74033rBdO9xgBwAAAOAvarlVYrtmzZrAY5qMTI+p8VDUWKf7bnLs5h16TJOISYcOHWwSrJ63ulyXettqvLMS5eHDhwdNWtatWzfbNVw9cEUJv663reQ8N95Z0JXkK39yb0qqXepBrORe76tJ1dTqfs4559gx5GedddYhlZeSbfUe1nW/tZ3qCazEe9CgQTbPyqsEx+2wjnzTF6GE2B2TrYkC9AWrRkcTnF166aV23PXQoUPt87qmt8Z2a+ZxzYT30EMP2SBU8KiLhLpQ6Pp2rVu3trVJ6mquLg3iXghes5e/8MILtku5/uoHpC9ewT527NjAuukHoK4b+vFoAgJvjVU0ek/VXm3u3duUDzPdPvwj2xizs0IFU3HXLmrqfIw4ALEA9gng+FB0lBwy5LB+nvKO7du3m8qVK9uGwEi5RVpamu961NLSHQeaNl9dzRV06orhvbi6qLu3Jke7+OKLbdKseg9ddF0Jt0uTEShB1lT3ap1W7ZBarL3UxePyyy+3M59rbIG6OLhT6Qe+4MREWymg8Rbu1PqxIkigGKhMwu17xAG8xwX2CSAOwD4BoXmHhreGS7j97n9TtiHf1DIdSuMY/vjjj7DL69pxmlQgGiXUuuXm9ttvt7fcqKuIEvNq1aqZ/GD2cigGljRoYJqsWGGS6BzjW8QBiAWwTwDHB0Q8T8jKMgsWLLCX89J4avwPSfdRTF035s+fb8aMGRM0m2DMmL0cCQlmf6lS/x8LJN3+RRyAWAD7BHB8QATqvfvXX38FLreF/yHpPorp+nG6rrcmOTj33HMLe3UAAAAAwHfocH8ET8yWG02kNmfOnFyX0bjyffv2mZdeeqmA1xAAAAAAkBck3YgeJNmasxh+j4GGq1cTCz5HHIBYAPsEcHxAJBrHrctpMZ47J7qXI6oEysj3FAOpe/f6vhz8jjgAsQD2CeD4gIjnCQkJpnz58hRQGLR0I6ospv33PcXAnKZNiQWfIw5ALIB9Ajg+IJLMzEwzc+ZM+xfBSLoB5EkWl34AcQD2CeDYAM4TkNv5YlYW5RMGSTcAAAAAAHFC0g0AAAAAQJyQdCN6kDB7ue8pBpotW0Ys+BxxAGIB7BPA8QGRaNbyli1bMnt5GCTdAPKkxMGDlBSIA7BPAMcGcJ6AyOeLJUpQOmGQdCOqbGYv9z3FwJxmzYgFnyMOQCyAfQI4PiC3SdRmzZrFZGphkHQDAAAAABAnJN0AAAAAAJB0AwAAAABQtCQ4juMU9krgyJSenm5SU1PNlt69TWrJkoW9OihEzn/H82r26gS+Cd8iDkAsgH0COD4UHSWHDDmsn6e0UuO6NYt5QkJCxNwiLS3NpKSkGD+hezmAPMkoXpySAnEA9gng2ADOExD5fDEjg9IJg6QbUTF7ORQDixo1IhZ8jjgAsQD2CeD4gEjUyj1v3jxmLw+DpBsAAAAAgDgh6QYAAAAAIE5IugHkSVJWFiUF4gDsE8CxAZwnIPL5YlISpRMGs5cjIneGwa29e5sUZi8HAAAAiozDPXt5NOnMXg5ExjXloBhIK1uWWPA54gDEAtgngOMDIp4nOI7ZvXu3/YtgdC9HVMxeDsXA8rp1iQWfIw5ALIB9Ajg+ILfZy5csWcLs5WGQdAMAAAAAECck3QAAAAAAxAlJN6JjXAYcx5Tav59Y8DviAMQC2CeA4wMiSEhIMMnJyfYvghULuQ/kkETS7XuKgeOXL/d9OfgdcQBiAewTwPEBEc8TkpJMq1atKKAwaOlGVNmUke8pBrZXqEAs+BxxAGIB7BPA8QERzxOys83WrVvtXwQj6UZUTiJh4neKgTU1ahALPkccgFgA+wRwfEAkSrZXrlxJ0h0G2RQAAAAAAHFC0g0AAAAAQJyQdCM6JlKD45iUvXuJBb8jDkAsgH0COD4gAs1anpqayuzlYTB7OaJi9nIoBhqtXk1B+BxxAGIB7BPA8QERzxOSkkzTpk0poDBIuhFV8YEDTcny5Skpn0+MsXHjRlO9enWTyMR6vkUcgFgA+wRwfADnCbGjezmiYtp/KAbWr19PLPgccQBiAewTwPEBnCfEjqQbAAAAAIA4IekGAAAAACBOSLoRPUgYw+t7ioEqVaoQCz5HHIBYAPsEcHwA5wmxS3AcrgeF8NLT0+20/2lpaSYlJYViAgAAAJAv6T7OLWjpRlRMpAbFwIoVK4gFnyMOQCyAfQI4PoDzhNiRdCMqkm4oBrZt20Ys+BxxAGIB7BPA8QGcJ8SOpBsAAAAAgDgpFq83RtHnDvfX+ItixQgVP8vMzDR//vknseBzxAGIBbBPAMcH5Pc8IT093f7145RiZFKIaMeOHfZvvXr1KCUAAAAAh2zPnj12QjU/IelGRBUrVrR/165d67sfBnLWTNaqVcusW7fOd7NN4n+IAxAL8GKfAGIBsewTHMexCXf16tV9V3Ak3Yh6fW4l3CRaEMUBsQDiAC5iAcQBvNgnIFocpPq0IY+J1AAAAAAAiBOSbgAAAAAA4oSkGxGVLFnSDBgwwP6FvxELIA7APgEcG8B5AjhfzJ8Ex49ztgMAAAAAcBjQ0g0AAAAAQJyQdAMAAAAAECck3QAAAAAAxAlJNyJ67bXXTN26dU2pUqVMmzZtzIwZMyito8jgwYPNKaecYsqVK2eqVq1qLrvsMrN06dKgZfbv32/69OljKlWqZMqWLWuuuOIKs2XLlqBl1q5day666CJTunRp+z7333+/yczMPMxbg4LyzDPPmISEBHPPPfcEHiMO/GPDhg3m73//u/3NJycnmxYtWphZs2YFntc0MI899pipVq2aff6cc84xy5YtC3qPnTt3muuuu85eo7V8+fLmpptuMnv37i2ErUF+ZGVlmf79+5t69erZ77hBgwbmqaeest+9izg4Ov3444/mkksuMdWrV7fHgc8++yzo+YL63ufNm2dOP/10e35Zq1Yt89xzzx2W7cOhx8HBgwfNgw8+aI8NZcqUsctcf/31ZuPGjUHvQRyEoYnUgFDvv/++U6JECeff//63s3DhQueWW25xypcv72zZsoXCOkp07tzZGTlypLNgwQJnzpw5zoUXXujUrl3b2bt3b2CZ2267zalVq5YzYcIEZ9asWU7btm2d9u3bB57PzMx0mjdv7pxzzjnO7Nmzna+//tqpXLmy069fv0LaKhyKGTNmOHXr1nVatmzp3H333YHHiQN/2Llzp1OnTh3nhhtucKZPn+6sXLnSGT9+vLN8+fLAMs8884yTmprqfPbZZ87cuXOdSy+91KlXr57z119/BZY5//zznVatWjm//vqrM3XqVKdhw4bONddcU0hbhVgNHDjQqVSpkvPVV185q1atcj766COnbNmyzssvvxxYhjg4OukY/sgjjzjjxo1TDYvz6aefBj1fEN97Wlqac8wxxzjXXXedPf8YO3ask5yc7IwYMeKwbivyFwe7d++253wffPCBs2TJEmfatGnOqaee6px00klB70Ec5ETSjbD0A+rTp0/gflZWllO9enVn8ODBlNhRauvWrXbnOmXKlMCOtXjx4vaEy7V48WK7jHay7o45MTHR2bx5c2CZ119/3UlJSXEOHDhQCFuB/NqzZ4/TqFEj5/vvv3c6duwYSLqJA/948MEHnQ4dOkR8Pjs72zn22GOd559/PvCY4qNkyZL2xFkWLVpk9xEzZ84MLPPNN984CQkJzoYNG+K8BSgIF110kdOzZ8+gxy6//HKbJAlx4A+hyVZBfe/Dhg1zKlSoEHSOoH1P48aND9OWIRbhKl/CVdhruTVr1tj7xEF4dC9HDhkZGea3336z3YZciYmJ9v60adMosaNUWlqa/VuxYkX7VzGgbkTeOGjSpImpXbt2IA70V12MjjnmmMAynTt3Nunp6WbhwoWHfRuQfxpGoGEC3u9biAP/+OKLL8zJJ59sunXrZoeKtG7d2rz55puB51etWmU2b94cFCOpqal2+JF3n6AupXofl5bXMWT69OmHeYuQH+3btzcTJkwwf/zxh70/d+5c89NPP5kLLrjA3icO/Kmgvnctc8YZZ5gSJUoEnTdoeNuuXbsO6zah4M4f1Q1d370QB+EVi/A4fGz79u12TJc3kRLdX7JkSaGtF+InOzvbjuE97bTTTPPmze1jOrjqoOjuRL1xoOfcZcLFifsciob333/f/P7772bmzJk5niMO/GPlypXm9ddfN/fee695+OGHbTzcdddddj/Qo0ePwG863G/eu09Qwu5VrFgxW5nHPqFoeOihh2zFqSpZk5KS7PnAwIED7ThdIQ78qaC+d/3VfAGh7+E+V6FChbhuBwqW5nzRGO9rrrnGjuMX4iA8km4AtpVzwYIFtjUD/rJu3Tpz9913m++//95OagN/V76phWrQoEH2vlq6tV8YPny4TbrhDx9++KEZPXq0GTNmjDn++OPNnDlzbKWsJkwiDgC41BvyyiuvtBPsqcIWuaN7OXKoXLmyrd0OnaVa94899lhK7Chzxx13mK+++spMmjTJ1KxZM/C4vmsNNdi9e3fEONDfcHHiPocjn7qPb9261Zx44om2RUK3KVOmmFdeecX+Xy0QxIE/aEbiZs2aBT3WtGlTe4UC7286t2OD/iqevHQ1A81kyz6haNAVKNTaffXVV9vhQ927dzf/+Mc/7BUvhDjwp4L63jlvOLoS7jVr1thKe7eVW4iD8Ei6kYO6Ep500kl2TJe3BUT327VrR4kdJVQzqYT7008/NRMnTszR3UsxULx48aA40JgrnYC7caC/8+fPDzrIujvf0JN3HJk6depkv0O1Zrk3tXaqK6n7f+LAHzS8JPSygRrXW6dOHft/7SN0MuXdJ6gbssZqevcJqqhTZY5L+xcdQzT2E0e+ffv22TG4XqqI13coxIE/FdT3rmV0SSolbd7zhsaNG9O1vIgl3Lpc3A8//GAvMelFHEQQYYI1+JwuGaYZKUeNGmVnIezVq5e9ZJh3lmoUbbfffru99MfkyZOdTZs2BW779u0LulSULiM2ceJEe8mwdu3a2VvoJcPOO+88e9mxb7/91qlSpQqXDCvivLOXC3HgD5qBtlixYvaSUcuWLXNGjx7tlC5d2nnvvfeCLhmkY8Hnn3/uzJs3z+nSpUvYSwa1bt3aXnbsp59+srPic8mwoqNHjx5OjRo1ApcM02WDdCnIBx54ILAMcXD0XsVCl//UTSnCkCFD7P/dWakL4nvXjOe6ZFj37t3tJcN0vqn9DJcMKxpxkJGRYS8VV7NmTXve5z1/9M5ITxzkRNKNiF599VWbcOl63bqEmK65iKOHdqThbrp2t0sH0t69e9vLe+ig2LVrV7tj9Vq9erVzwQUX2Ots6sSsb9++zsGDBwthixCvpJs48I8vv/zSVqSp0rVJkybOG2+8EfS8LhvUv39/e9KsZTp16uQsXbo0aJkdO3bYk2xd21mXD7zxxhvtSRyKhvT0dPv71/G/VKlSTv369e01e70n1MTB0WnSpElhzwtUEVOQ37uu8a3LE+o9VMGjZB5FIw5UERfp/FGvcxEHOSXon0it4AAAAAAAIP8Y0w0AAAAAQJyQdAMAAAAAECck3QAAAAAAxAlJNwAAAAAAcULSDQAAAABAnJB0AwAAAAAQJyTdAAAAAACQdAMAAAAAULTQ0g0AAAAAQJyQdAMAUEhWr15tEhISzJw5cwr1O3j88cfNCSecUGif379/f9OrV69Deo/Jkyfbsty9e7e9P2rUKFO+fPmI23jDDTeYyy67zBRloducm0WLFpmaNWuaP//887CsGwDgf0i6AQBHtM2bN5s777zT1K9f35QsWdLUqlXLXHLJJWbChAmmqNO2bNq0yTRv3jxun1G3bl2bmEW6Kfm87777Cq089f2+/PLL5pFHHjmk92nfvr0ty9TU1Dwtr89UYu4XzZo1M23btjVDhgwp7FUBAN8pVtgrAABAbi3Bp512mm2xfP75502LFi3MwYMHzfjx402fPn3MkiVLjujC07oWL1484vNJSUnm2GOPjes6zJw502RlZdn///LLL+aKK64wS5cuNSkpKfax5ORkU7ZsWXsrDG+99ZZNmOvUqXNI71OiRImYyjKvyfnR5MYbbzS33HKL6devnylWjFNAADhcaOkGAByxevfubVtjZ8yYYZPF4447zhx//PHm3nvvNb/++mtgubVr15ouXbrYxFHJ5JVXXmm2bNmSo2vxu+++a1t+lXBdffXVZs+ePfb5N954w1SvXt1kZ2cHfb7es2fPnoH7n3/+uTnxxBNNqVKlbMv7E088YTIzMwPPa11ff/11c+mll5oyZcqYgQMHml27dpnrrrvOVKlSxSa4jRo1MiNHjozYvXzKlCnm1FNPta361apVMw899FDQZ5x55pnmrrvuMg888ICpWLGiTTS1fZHoc7WMblpeqlatGnhMZRGp6/WgQYPMMcccYys9nnzySbse999/v30fdVV2t8O1bt06W/ZaXsuo/LSNuXn//fdtzwUvbaN6N9xzzz2mQoUKdh3efPNN2zVaiWO5cuVMw4YNzTfffJOvrtbebXR9/PHHtlJH31GlSpXMOeecE9QVW5UDTZs2td99kyZNzLBhw4Leb/369eaaa66x263v/uSTTzbTp08PPK+4aNCgga0caNy4sY1FL627PqNr166mdOnSNk6++OKLoGW+/vpr+xvQOp511lk5ynbNmjW2LFVmWgf9VvQa17nnnmt27txpYwwAcPiQdAMAjkhKDr799lvboq0EIpQ7XleJspI7N5n4/vvvzcqVK81VV10VtPyKFSvMZ599Zr766it707LPPPOMfa5bt25mx44dZtKkSTk+XwmzTJ061Vx//fXm7rvvtuNjR4wYYbsnK7H2UgKrxGn+/Pk2Ydd4ZS2vBHHx4sU2+apcuXLYbd6wYYO58MILzSmnnGLmzp1rl/3Xv/5lnn766aDl3n77bVsmSuqee+45mxBruwvSxIkTzcaNG82PP/5ouyQPGDDAXHzxxTah0+fedttt5tZbb7XJptuq37lzZ5sQq6x+/vlnWwly/vnnm4yMjLCfoTJW2ShBDaVtVDmpwkUJ+O23326/J7WK//777+a8884z3bt3N/v27TvkbVW3dCXM+r70HSmBv/zyy43jOPb50aNHm8cee8x+13pelRH6XrWOsnfvXtOxY0f7/SlR1nenShG3EufTTz+1cdO3b1+zYMECW26qPPDGm6gSR5UW8+bNs3Gg2FMZuRUaWicl1aqkufnmm22FjJd+KwcOHLDfmeLv2WefDerBoIRflSv6fgAAh5EDAMARaPr06cp4nHHjxuW63HfffeckJSU5a9euDTy2cOFC+9oZM2bY+wMGDHBKly7tpKenB5a5//77nTZt2gTud+nSxenZs2fg/ogRI5zq1as7WVlZ9n6nTp2cQYMGBX32u+++61SrVi1wX595zz33BC1zySWXODfeeGPYdV+1apV9zezZs+39hx9+2GncuLGTnZ0dWOa1115zypYtG1iPjh07Oh06dAh6n1NOOcV58MEHnWgmTZpkP2/Xrl1Bj6t8WrVqFbjfo0cPp06dOoHPFK3X6aefHrifmZnplClTxhk7dmygLELX/cCBA05ycrIzfvz4sOuj7db6eL+7cNvoflb37t0Dj23atMm+dtq0aWG3beTIkU5qamqu26jvXH777Tf72tWrV4ddzwYNGjhjxowJeuypp55y2rVrF4iVcuXKOTt27Aj7+vbt2zu33HJL0GPdunVzLrzwwsB9ff6jjz4auL9371772DfffGPv9+vXz2nWrFnQe+g7925zixYtnMcff9zJTdeuXZ0bbrgh12UAAAWLlm4AwBHJbWWMRi2PmpBMN++kUWoJ13MudStXK6xLXbe3bt0auK9WxU8++cS2FLqtm+qCnpj4/4dKtV6qRdkd/6ybxseqldTb2hraaqsWWnWhVgujWj81rjq3bWnXrp3tauzSmHa1pLotytKyZcug14VuS0FQ12R320VdvNX92jseXd2w3c9V+SxfvtyWsVs+6mq9f/9+28sgnL/++sv+VZftUN5tdD/L+/laHymI7W7VqpXp1KmTfX+1pqsru4YFiLqYa/1vuummoO9evQ/c7VLLc+vWrQPd98N9r/oevXTfG5+h26yeDBoq4W6flm3Tpk3Q8ooVLw070HrpvdUzQS3modQ1vSB6BwAA8o5ZNAAARySNaVXyWVCTpYVOaKb39o7hVrddJfr/+c9/bPdudcF96aWXAs8r8VX3X3XxDeVNGkO7wl9wwQV2rK3G1qoLuJI7dQN+4YUX4rYtBSHcZ+T2uSqfk046yVZWhBtXHo7bzV4Jbugy0T7frZgoiO1WUq/vRhUi3333nXn11VftbOrqRq/x1aJEPDTp1evcRLYgHOr3qi7n6uKvGNZ2DB482Lz44ou2e75L3dU1thwAcPjQ0g0AOCKp1VAJxGuvvRb22sLuhFma3ErjXXVzaZywnleLd14pcVZCraRx7NixdrIrTZrm0v8167cm8Aq9eVuEw1FC2aNHD/Pee++ZoUOH2onbwtG2TJs2LaiVX2Oj1XqsicuOZCqfZcuW2UnaQssn0kzhSv7Umqvvq7ApwVULsSpWZs+ebcc/ayy2WtQ1yZ7mCQjdrnr16gVaqNXa7Y6/Dve96nv00v1Y4lPvofHtXt7JBF3q8aHx9uPGjbNjyFVZ4KUx5WqVBwAcPiTdAIAjlhJuXe5Ks3mr67eSOnWzfeWVVwJdazXLtLoFq3u4JthSYqIJzzSxVbgJunKj91Ar4b///e/ABGouTaT1zjvv2KRs4cKFdj3UbfzRRx/N9T31Os16rq7Xep0mcVMCFWm2dlUeqGVSLfx6nboJa7b2aIl9YVN5qeVak9qpl8CqVavshGTq8uztGu+lbdL399NPP5nCpBZtTY42a9YsOxO+EtZt27YFvid952o1Vtz98ccfdpIyzdzuXvNak7BpJnjNhq5kWgm64lUVKKIZ3zXpnibGUwzrdfoMXR89r5RI67V6L1X+jBkzJsd1xjXbuy6np7LXb0ETtXljTbOda7I3lTkA4PA5so/gAABf02W5lDzo8khqtWvevLm97NGECRNsAuO2UCo51azaZ5xxhk0o9LoPPvgg5s87++yzbQu7kpprr7026Dm1uithVrdddT9v27at7X4e7frSajHVdZHVGqr1U5dkJevh1KhRw3ZDV8WBxhkr0dJY4miJ/ZFA3bA1a3bt2rVtjwEle1p3jel2rwkeqUu0yqOgu8fHQuunddeM4bokl8pb3bI1NMBdR13OS4m2KnhUoaOE123p1nesuFArv95Dy2hmfLf7uZLxl19+2Q4p0Fh5zXyv99Kl0fJK5apEXjPwKzaGDx9uKwq8VEGloQsqe80ar23xXtpMPTg06/uhXhMdABCbBM2mFuNrAAAACoROQzRW+h//+IdtMUZ86LJtmidBLeShk7oBAOKLlm4AAFBo1FNBY9wzMzP5FuJI3eYffvhhEm4AKAS0dAMAAAAAECe0dAMAAAAAECck3QAAAAAAxAlJNwAAAAAAcULSDQAAAABAnJB0AwAAAAAQJyTdAAAAAADECUk3AAAAAABxQtINAAAAAECckHQDAAAAAGDi4/8A7Cw5AcHjIWIAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Data for plotting\n", + "labels = ['Kloppy', 'TDC (Zero-Copy)']\n", + "times = [kloppy_conv_time * 1000, tdc_conv_time * 1000]\n", + "colors = ['#f87171', '#34d399'] # Red, Green\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 3))\n", + "bars = ax.barh(labels, times, color=colors)\n", + "\n", + "# Add titles and labels\n", + "ax.set_xlabel('Conversion Time (milliseconds)')\n", + "ax.set_title('Benchmark: Polars Conversion Performance')\n", + "ax.grid(axis='x', linestyle='--', alpha=0.7)\n", + "\n", + "# Add value labels\n", + "for bar in bars:\n", + " width = bar.get_width()\n", + " ax.text(width + 5, bar.get_y() + bar.get_height()/2, \n", + " f'{width:.2f}ms', ha='left', va='center', fontweight='bold')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/03-interop.ipynb b/examples/03-interop.ipynb new file mode 100644 index 0000000..03e1c06 --- /dev/null +++ b/examples/03-interop.ipynb @@ -0,0 +1,228 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Interoperability: Zero-Copy Data Exchange\n", + "\n", + "One of the core strengths of the `tracking-data-container` (TDC) is its ability to seamlessly exchange data with the broader Python data science ecosystem. TDC is built on **Apache Arrow**, a language-independent columnar memory format. Because Arrow defines a standardized way to represent data in memory, different libraries can share that data without the need for expensive \"serialization\" or \"copying\".\n", + "\n", + "This notebook illustrates how to convert a `TrackingDataContainer` to other popular formats like **Apache Arrow**, **Polars**, **Pandas**, and **Parquet**." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Container created with 3 rows.\n" + ] + } + ], + "source": [ + "from datetime import datetime, timedelta, timezone\n", + "\n", + "import tracking_data_container as tdc\n", + "\n", + "# Create a sample container\n", + "base_time = datetime(2023, 5, 27, 13, 30, 0, tzinfo=timezone.utc)\n", + "data = {\n", + " \"frame_id\": [1000, 1001, 1002],\n", + " \"period\": [\"first_half\", \"first_half\", \"first_half\"],\n", + " \"timestamp\": [\n", + " base_time,\n", + " base_time + timedelta(milliseconds=40),\n", + " base_time + timedelta(milliseconds=80),\n", + " ],\n", + " \"ball/x\": [50.0, 51.0, 52.0],\n", + " \"ball/y\": [12.0, 13.0, 14.0],\n", + " \"teams/home/players/p1/x\": [23.5, 23.6, 23.7],\n", + "}\n", + "cs = tdc.CoordinateSystem(\n", + " x_range=(0, 100),\n", + " y_range=(0, 100),\n", + " origin=tdc.Origin.TopLeft,\n", + " length_unit=tdc.LengthUnit.Percent,\n", + ")\n", + "container = tdc.TrackingDataContainer(data, validate=False).with_coordinate_system(cs)\n", + "print(f\"Container created with {container.num_rows()} rows.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Apache Arrow\n", + "\n", + "You can extract the underlying data as a `pyarrow.RecordBatch`. This is the most \"raw\" representation of the data and is useful if you are working with other tools in the Arrow ecosystem." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type: \n", + "Number of columns: 6\n" + ] + } + ], + "source": [ + "arrow_batch = container.to_arrow()\n", + "print(f\"Type: {type(arrow_batch)}\")\n", + "print(f\"Number of columns: {arrow_batch.num_columns}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Polars\n", + "\n", + "Polars is a blazingly fast DataFrame library also built on Apache Arrow. Converting to a Polars DataFrame is the recommended way to perform complex data manipulations in Python." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type: \n", + "Polars DataFrame:\n", + "shape: (3, 6)\n", + "┌──────────┬────────────┬─────────────────────────┬────────┬────────┬─────────────────────────┐\n", + "│ frame_id ┆ period ┆ timestamp ┆ ball/x ┆ ball/y ┆ teams/home/players/p1/x │\n", + "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n", + "│ u64 ┆ str ┆ datetime[μs] ┆ f32 ┆ f32 ┆ f32 │\n", + "╞══════════╪════════════╪═════════════════════════╪════════╪════════╪═════════════════════════╡\n", + "│ 1000 ┆ first_half ┆ 2023-05-27 13:30:00 ┆ 50.0 ┆ 12.0 ┆ 23.5 │\n", + "│ 1001 ┆ first_half ┆ 2023-05-27 13:30:00.040 ┆ 51.0 ┆ 13.0 ┆ 23.6 │\n", + "│ 1002 ┆ first_half ┆ 2023-05-27 13:30:00.080 ┆ 52.0 ┆ 14.0 ┆ 23.700001 │\n", + "└──────────┴────────────┴─────────────────────────┴────────┴────────┴─────────────────────────┘\n" + ] + } + ], + "source": [ + "df_polars = container.to_polars()\n", + "print(f\"Type: {type(df_polars)}\")\n", + "print(\"Polars DataFrame:\")\n", + "print(df_polars.head())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pandas\n", + "\n", + "For traditional data science workflows, you can convert the container to a Pandas DataFrame. Note that while this is highly convenient, Pandas does not use Arrow memory internally, so a copy might be performed depending on the data types and your Pandas version." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type: \n", + "Pandas DataFrame:\n", + " frame_id period timestamp ball/x ball/y \\\n", + "0 1000 first_half 2023-05-27 13:30:00.000 50.0 12.0 \n", + "1 1001 first_half 2023-05-27 13:30:00.040 51.0 13.0 \n", + "2 1002 first_half 2023-05-27 13:30:00.080 52.0 14.0 \n", + "\n", + " teams/home/players/p1/x \n", + "0 23.500000 \n", + "1 23.600000 \n", + "2 23.700001 \n" + ] + } + ], + "source": [ + "df_pandas = container.to_pandas()\n", + "print(f\"Type: {type(df_pandas)}\")\n", + "print(\"Pandas DataFrame:\")\n", + "print(df_pandas.head())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parquet\n", + "\n", + "Parquet is a high-performance storage format designed for columnar data. TDC allows you to save your container directly to Parquet, keeping track of the coordinate system and orientation." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saved to ./data/match_data_sample.parquet\n", + "Reloaded container has 3 rows.\n", + "Reloaded container uses \n" + ] + } + ], + "source": [ + "parquet_file = \"./data/match_data_sample.parquet\"\n", + "container.to_parquet(parquet_file)\n", + "print(f\"Saved to {parquet_file}\")\n", + "\n", + "# You can also load it back directly into a container\n", + "reloaded = tdc.TrackingDataContainer.from_parquet(parquet_file)\n", + "print(f\"Reloaded container has {reloaded.num_rows()} rows.\")\n", + "print(f\"Reloaded container uses {reloaded.coordinate_system}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/04-udfs.ipynb b/examples/04-udfs.ipynb new file mode 100644 index 0000000..6d564ae --- /dev/null +++ b/examples/04-udfs.ipynb @@ -0,0 +1,322 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Computed Metrics and Performance\n", + "\n", + "Adding computed metrics, such as ball speed or player velocity, is a core part of tracking data analysis. The TDC supports multiple ways to efficiently add data, which enables interoperability between packages. This notebook compares different execution strategies for adding columns and benchmarks their performance." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 7500 frames.\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "import polars as pl\n", + "import tracking_data_container as tdc\n", + "\n", + "# Load the sample data\n", + "POS_FILE = \"./data/DFL_04_03_positions_raw_observed_DFL-COM-000001_DFL-MAT-J03WMX.xml\"\n", + "META_FILE = \"./data/DFL_02_01_matchinformation_DFL-COM-000001_DFL-MAT-J03WMX.xml\"\n", + "\n", + "container = tdc.load_sportec(POS_FILE, META_FILE)\n", + "print(f\"Loaded {container.num_rows()} frames.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Execution Strategies\n", + "\n", + "TDC supports multiple ways to add computed columns, balancing ease-of-use with raw performance. We will add a velocity column for every player using four different methods:\n", + "\n", + "1. **TDC Expression Engine**: Uses the internal DataFusion engine. Flexible and supports lazy evaluation.\n", + "2. **Vectorized Python Functions**: Recommended for custom Python logic. Uses zero-copy Arrow exchange to perform calculations in a single batched pass.\n", + "3. **Optimized & 3rd-Party Rust Kernels**: High-performance Rust functions that operate directly on Arrow arrays via the Arrow C Data Interface.\n", + "4. **Native Polars Batch**: Converting the entire container to a Polars DataFrame for processing." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. TDC Expression Engine\n", + "\n", + "The expression engine allows building logical plans that can be optimized and executed in Rust. While powerful, it currently has some overhead for eager per-player additions." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expression Engine time: 0.1285s\n" + ] + } + ], + "source": [ + "start = time.perf_counter()\n", + "expr_fn = lambda x, y: (tdc.col(x).diff().pow(2.0) + tdc.col(y).diff().pow(2.0)).sqrt()\n", + "container_expr = container.add_column_per_player(\"vel\", expr_fn, on=[\"x\", \"y\"])\n", + "print(f\"Expression Engine time: {time.perf_counter() - start:.4f}s\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Vectorized Python Functions\n", + "\n", + "By passing a callable that operates on Arrow arrays (which can be converted to Polars or NumPy), we leverage TDC's zero-copy integration. This is often the fastest way to implement custom Python-based metrics because it avoids the quadratic cost of iterative updates." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Polars Vectorized time: 0.0992s\n" + ] + } + ], + "source": [ + "import polars as pl\n", + "\n", + "def compute_speed_polars(x, y):\n", + " # Inputs are pyarrow.Array objects\n", + " x, y = pl.Series(x), pl.Series(y)\n", + " return (x.diff().fill_null(0) ** 2 + y.diff().fill_null(0) ** 2).sqrt()\n", + "\n", + "start = time.perf_counter()\n", + "container_polars_vec = container.add_column_per_player(\"vel\", compute_speed_polars, on=[\"x\", \"y\"])\n", + "print(f\"Polars Vectorized time: {time.perf_counter() - start:.4f}s\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Numpy Vectorized time: 0.0076s\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "def compute_speed_numpy(x, y):\n", + " x = x.to_numpy(zero_copy_only=False)\n", + " y = y.to_numpy(zero_copy_only=False)\n", + " return np.hypot(np.diff(x, prepend=x[0]), np.diff(y, prepend=y[0]))\n", + "\n", + "start = time.perf_counter()\n", + "container_numpy_vec = container.add_column_per_player(\"vel\", compute_speed_numpy, on=[\"x\", \"y\"])\n", + "print(f\"Numpy Vectorized time: {time.perf_counter() - start:.4f}s\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Optimized & 3rd-Party Rust Kernels (Arrow-Native)\n", + "\n", + "TDC supports the **Arrow C Data Interface** (via `__arrow_c_array__`), allowing you to use hand-optimized Rust kernels from any 3rd-party Rust library. These kernels operate directly on Arrow capsules, avoiding Python overhead.\n", + "\n", + "#### Python Usage\n", + "As an example, a few common metrics like `speed` are implemented as Rust kernels in the `tdc.ops` module. You can pass them directly to `add_column` or `add_column_per_player`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimized Rust Kernel time: 0.0046s\n" + ] + } + ], + "source": [ + "start = time.perf_counter()\n", + "container_rust = container.add_column_per_player(\"vel\", tdc.ops.compute_speed, on=[\"x\", \"y\"])\n", + "print(f\"Optimized Rust Kernel time: {time.perf_counter() - start:.4f}s\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Implementing Your Own Kernel\n", + "\n", + "If you have a 3rd-party Rust library `my_rust_lib` that exposes a function taking and returning Arrow capsules, you can use it directly in TDC. A typical implementation using `pyo3` and `arrow-rs` would look like this:\n", + "\n", + "```rust\n", + "#[pyfunction]\n", + "fn compute_metric(py: Python<'_>, array: Bound<'_, PyAny>) -> PyResult {\n", + " // 1. Import from capsule (Zero-copy)\n", + " let (array_cap, schema_cap) = array.call_method0(\"__arrow_c_array__\")?\n", + " .extract::<(Bound<'_, PyCapsule>, Bound<'_, PyCapsule>)>()?;\n", + " let input_arr = pyo3_arrow::PyArray::from_arrow_pycapsule(&array_cap, &schema_cap)?;\n", + " let array = input_arr.into_inner().0;\n", + " \n", + " // 2. Vectorized calculation in Rust using arrow-rs\n", + " let result = /* ... logic ... */;\n", + " \n", + " // 3. Export back as capsule (Zero-copy)\n", + " Ok(pyo3_arrow::PyArray::new(result, Arc::new(field)).into_py(py))\n", + "}\n", + "```\n", + "\n", + "You can then use it in Python just like the built-in kernels:\n", + "\n", + "```python\n", + "import my_rust_lib\n", + "container.add_column(\"custom_metric\", my_rust_lib.compute_metric, on=[\"x\"])\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Native Polars Batch\n", + "\n", + "You can also convert the entire container to Polars, perform all transformations, and re-import it. The re-import is zero-copy using Arrow capsules." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Native Polars Batch time: 0.0964s\n" + ] + } + ], + "source": [ + "start = time.perf_counter()\n", + "df = container.to_polars()\n", + "exprs = []\n", + "for player in container.schema().players():\n", + " x = tdc.DataField(entity=player, attribute=\"x\").name()\n", + " y = tdc.DataField(entity=player, attribute=\"y\").name()\n", + " out = tdc.DataField(entity=player, attribute=\"vel\").name()\n", + " exprs.append(\n", + " (pl.col(x).diff().fill_null(0) ** 2 + pl.col(y).diff().fill_null(0) ** 2).sqrt().alias(out)\n", + " )\n", + "\n", + "df = df.with_columns(exprs)\n", + "container_polars_native = tdc.TrackingDataContainer(df)\n", + "print(f\"Native Polars Batch time: {time.perf_counter() - start:.4f}s\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Verification\n", + "\n", + "We verify that all execution strategies produce consistent results for a sample player." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Max difference Expr vs Polars: 0.0\n", + "Max difference Expr vs Numpy: 5.551115123125783e-17\n", + "Max difference Expr vs Rust: 0.0\n", + "\n", + "Verification Successful: All methods produced identical results.\n" + ] + } + ], + "source": [ + "p1 = container.schema().players()[0]\n", + "vel_col = tdc.DataField(entity=p1, attribute=\"vel\").name()\n", + "\n", + "v_expr = container_expr.to_polars()[vel_col].fill_null(0)\n", + "v_polars = container_polars_vec.to_polars()[vel_col].fill_null(0)\n", + "v_numpy = container_numpy_vec.to_polars()[vel_col].fill_null(0)\n", + "v_rust = container_rust.to_polars()[vel_col].fill_null(0)\n", + "\n", + "mismatch_polars = (v_expr - v_polars).abs().max()\n", + "mismatch_numpy = (v_expr - v_numpy).abs().max()\n", + "mismatch_rust = (v_expr - v_rust).abs().max()\n", + "\n", + "print(f\"Max difference Expr vs Polars: {mismatch_polars}\")\n", + "print(f\"Max difference Expr vs Numpy: {mismatch_numpy}\")\n", + "print(f\"Max difference Expr vs Rust: {mismatch_rust}\")\n", + "\n", + "if mismatch_polars < 1e-6 and mismatch_rust < 1e-6:\n", + " print(\"\\nVerification Successful: All methods produced identical results.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/05-lazy.ipynb b/examples/05-lazy.ipynb new file mode 100644 index 0000000..8c24895 --- /dev/null +++ b/examples/05-lazy.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lazy Evaluation\n", + "\n", + "Loading massive datasets into memory is often unnecessary if you only need a small subset of the data. TDC supports **lazy evaluation** using **DataFusion**, allowing you to define complex queries that are optimized and executed only when needed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You might wonder: *why does TDC implement its own lazy execution instead of relying on Polars and DuckDB?* \n", + "\n", + "1. **Metadata-Aware Optimization**: Our lazy engine can optimize selections based on the sports domain (e.g., \"only load coordinates for the active players\").\n", + "2. **Domain-Specific Pushdown**: Beyond simple filters, TDC can push down sports logic (like \"filter frames by ball-in-play status\") into the scanner, which generic engines cannot easily optimize without custom functions.\n", + "3. **Seamless Metadata Propagation**: When you perform a lazy query in TDC, the resulting eager container still has its schema, coordinate systems, and orientation metadata intact. Converting to Polars too early loses this domain context.\n", + "4. **Unified API**: You can use the same selectors (`container.selector.ball()`) and transformations whether you are working with an in-memory dataset or scanning 100GB of Parquet files.\n", + "\n", + "*(that aside, I think this should have low priority)*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What is Lazy Evaluation?\n", + "\n", + "In \"eager\" mode, every operation (like loading a file or adding a column) happens immediately. In \"lazy\" mode, these operations are recorded in a **logical plan**. This allows the engine to:\n", + "1. **Push down filters**: Only read the rows from a Parquet file that match your criteria.\n", + "2. **Prune columns**: Only load the columns required for your final result.\n", + "3. **Optimize memory**: Avoid allocating large buffers for intermediate steps." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from pathlib import Path\n", + "\n", + "import tracking_data_container as tdc\n", + "\n", + "PARQUET_FILE = \"data/match_data.parquet\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scanning Parquet Lazily\n", + "\n", + "The `scan_parquet` method returns a `LazyTrackingDataContainer`. This operation is instantaneous because it only reads the file metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Lazy container initialized.\n" + ] + } + ], + "source": [ + "lazy_container = tdc.scan_parquet(PARQUET_FILE)\n", + "print(\"Lazy container initialized.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining a Query Plan\n", + "\n", + "You can chain filters and selections. Notice that no data processing happens yet. We can use the same powerful TDC selectors here." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query plan defined.\n" + ] + } + ], + "source": [ + "# Define a query: only ball coordinates from the first half\n", + "query = (\n", + " lazy_container\n", + " .filter(tdc.col(\"period\").eq(tdc.lit(\"first_half\")))\n", + " .select([\"frame_id\", \"ball/**\"])\n", + ")\n", + "print(\"Query plan defined.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Executing the Plan\n", + "\n", + "To get the results, call `.collect()`. The internal engine will optimize the plan and return an eager `TrackingDataContainer` containing only the requested data." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collected 1500 frames in 0.0169s.\n", + "shape: (5, 6)\n", + "┌──────────┬────────┬────────┬──────────┬────────┬──────────┐\n", + "│ frame_id ┆ ball/z ┆ ball/y ┆ ball/vel ┆ ball/x ┆ ball/acc │\n", + "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n", + "│ i64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │\n", + "╞══════════╪════════╪════════╪══════════╪════════╪══════════╡\n", + "│ 10000 ┆ 0.0 ┆ -0.17 ┆ 1.98 ┆ 0.18 ┆ 0.0 │\n", + "│ 10001 ┆ 0.0 ┆ -0.19 ┆ 2.02 ┆ -0.4 ┆ 0.0 │\n", + "│ 10002 ┆ 0.01 ┆ -0.21 ┆ 2.02 ┆ -0.97 ┆ 0.0 │\n", + "│ 10003 ┆ 0.01 ┆ -0.24 ┆ 2.02 ┆ -1.54 ┆ 0.0 │\n", + "│ 10004 ┆ 0.02 ┆ -0.26 ┆ 2.02 ┆ -2.12 ┆ 0.0 │\n", + "└──────────┴────────┴────────┴──────────┴────────┴──────────┘\n" + ] + } + ], + "source": [ + "start = time.perf_counter()\n", + "result = query.collect()\n", + "end = time.perf_counter()\n", + "\n", + "print(f\"Collected {result.num_rows()} frames in {end - start:.4f}s.\")\n", + "print(result.to_polars().head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/06-skeletal.ipynb b/examples/06-skeletal.ipynb new file mode 100644 index 0000000..be4ab76 --- /dev/null +++ b/examples/06-skeletal.ipynb @@ -0,0 +1,226969 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hawk-Eye Skeletal Tracking Data\n", + "\n", + "This notebook illustrates how to load Hawk-Eye tracking data, including full skeletal information (landmarks), and how to efficiently work with this high-dimensional data using `tracking-data-container`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import re\n", + "import time\n", + "import psutil\n", + "from pathlib import Path\n", + "from typing import Union\n", + "import polars as pl\n", + "import tracking_data_container as tdc\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.animation import FuncAnimation\n", + "from IPython.display import HTML\n", + "\n", + "def get_memory_usage():\n", + " process = psutil.Process(os.getpid())\n", + " return process.memory_info().rss / (1024 * 1024) # MB\n", + "\n", + "def _natural_sort_key(path: Union[str, Path]) -> list[Union[int, str]]:\n", + " # Split string into list of chunks for natural sorting\n", + " return [\n", + " int(text) if text.isdigit() else text.lower()\n", + " for text in re.split(r\"(\\d+)\", str(path))\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Load the full dataset\n", + "\n", + "We will load all 90+ minutes of data, including ball tracking, player centroids, and joint positions." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 95 minutes of data.\n", + "Loaded 132917 frames in 28.34 seconds.\n", + "Memory used: 2283.92 MB\n", + "Total columns: 3556\n" + ] + } + ], + "source": [ + "data_dir = Path(\"../data/hawkeye\")\n", + "ball_files = sorted(list(data_dir.glob(\"*.samples.ball.json\")), key=_natural_sort_key)\n", + "centroid_files = sorted(list(data_dir.glob(\"*.samples.centroids.json\")), key=_natural_sort_key)\n", + "joint_files = sorted(list(data_dir.glob(\"*.samples.joints.json\")), key=_natural_sort_key)\n", + "meta_file = data_dir / \"meta.json\"\n", + "\n", + "print(f\"Found {len(ball_files)} minutes of data.\")\n", + "\n", + "start_mem = get_memory_usage()\n", + "start_time = time.time()\n", + "\n", + "container = tdc.load_hawkeye(\n", + " ball_files, \n", + " centroid_files, \n", + " joint_paths=joint_files, \n", + " meta_path=meta_file\n", + ")\n", + "\n", + "end_time = time.time()\n", + "end_mem = get_memory_usage()\n", + "\n", + "print(f\"Loaded {container.num_rows()} frames in {end_time - start_time:.2f} seconds.\")\n", + "print(f\"Memory used: {end_mem - start_mem:.2f} MB\")\n", + "print(f\"Total columns: {len(container.schema().names())}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Convert to Parquet\n", + "\n", + "Parquet is a columnar storage format that is highly efficient for large datasets. `tracking-data-container` allows seamless export to Parquet, which preserves the standardized schema and metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data written to ./data/hawkeye_full.parquet\n", + "File size: 1646.18 MB\n" + ] + } + ], + "source": [ + "parquet_path = \"./data/hawkeye_full.parquet\"\n", + "container.to_parquet(parquet_path)\n", + "print(f\"Data written to {parquet_path}\")\n", + "print(f\"File size: {os.path.getsize(parquet_path) / (1024 * 1024):.2f} MB\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Efficient Loading for Specific Players\n", + "\n", + "One of the key advantages of using TDC with Parquet is the ability to lazily scan the data and only load the columns you actually need. Here we show how to load only the skeletal data for a single player." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 118 columns for player #10 in 0.2992 seconds.\n", + "Memory used for selection: -214.36 MB\n" + ] + } + ], + "source": [ + "# Scan the Parquet file lazily\n", + "lazy_container = tdc.scan_parquet(parquet_path)\n", + "\n", + "# Select a specific player (e.g., player #10 on the home team)\n", + "player_selector = lazy_container.selector.player(\"10\", team_id=\"home\")\n", + "\n", + "start_mem = get_memory_usage()\n", + "start_time = time.time()\n", + "\n", + "# Collect the data for the selected player only\n", + "player_data = lazy_container.select(player_selector).collect()\n", + "\n", + "end_time = time.time()\n", + "end_mem = get_memory_usage()\n", + "\n", + "print(f\"Loaded {len(player_data.schema().names())} columns for player #10 in {end_time - start_time:.4f} seconds.\")\n", + "print(f\"Memory used for selection: {end_mem - start_mem:.2f} MB\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Animate 3D Pose Tracking\n", + "\n", + "Now we will animate the 3D pose tracking data for our selected player. We will define the skeletal connections and use `matplotlib` to render the animation." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Define the skeletal connections for Hawk-Eye joints\n", + "CONNECTIONS = [\n", + " (\"nose\", \"lEye\"), (\"nose\", \"rEye\"),\n", + " (\"lEye\", \"lEar\"), (\"rEye\", \"rEar\"),\n", + " (\"neck\", \"lShoulder\"), (\"neck\", \"rShoulder\"),\n", + " (\"lShoulder\", \"rShoulder\"),\n", + " (\"lShoulder\", \"lElbow\"), (\"lElbow\", \"lWrist\"),\n", + " (\"rShoulder\", \"rElbow\"), (\"rElbow\", \"rWrist\"),\n", + " (\"lShoulder\", \"lHip\"), (\"rShoulder\", \"rHip\"),\n", + " (\"lHip\", \"rHip\"),\n", + " (\"lHip\", \"midHip\"), (\"rHip\", \"midHip\"),\n", + " (\"lHip\", \"lKnee\"), (\"lKnee\", \"lAnkle\"),\n", + " (\"rHip\", \"rKnee\"), (\"rKnee\", \"rAnkle\"),\n", + " (\"lAnkle\", \"lHeel\"), (\"lAnkle\", \"lBigToe\"), (\"lBigToe\", \"lSmallToe\"),\n", + " (\"rAnkle\", \"rHeel\"), (\"rAnkle\", \"rBigToe\"), (\"rBigToe\", \"rSmallToe\"),\n", + "]\n", + "\n", + "def animate_player(container, player_id, team=\"home\", start_frame=0, num_frames=150):\n", + " # Select player skeletal data and ball data\n", + " selector = [container.selector.player(player_id, team_id=team), container.selector.ball()]\n", + " data = container.slice(start_frame, num_frames).select(selector).to_polars()\n", + " \n", + " fig = plt.figure(figsize=(10, 8))\n", + " ax = fig.add_subplot(111, projection='3d')\n", + " \n", + " # Base prefix for player landmarks\n", + " prefix = f\"teams/{team}/players/{player_id}/landmarks/\"\n", + " \n", + " # Initialize lines for skeleton and point for ball\n", + " lines = [ax.plot([], [], [], color='blue', lw=2)[0] for _ in CONNECTIONS]\n", + " ball_point, = ax.plot([], [], [], 'ro', markersize=10, label=\"Ball\")\n", + " \n", + " # Determine limits based on player position\n", + " player_x = data.get_column(f\"{prefix}midHip/x\").mean()\n", + " player_y = data.get_column(f\"{prefix}midHip/y\").mean()\n", + " \n", + " ax.set_xlim(player_x - 3, player_x + 3)\n", + " ax.set_ylim(player_y - 3, player_y + 3)\n", + " ax.set_zlim(0, 3)\n", + " ax.set_xlabel(\"X (m)\")\n", + " ax.set_ylabel(\"Y (m)\")\n", + " ax.set_zlabel(\"Z (m)\")\n", + " ax.set_title(f\"Player {player_id} ({team}) - 3D Pose Animation\")\n", + " \n", + " def update(frame):\n", + " row = data.row(frame, named=True)\n", + " \n", + " # Update skeleton lines\n", + " for i, (start, end) in enumerate(CONNECTIONS):\n", + " try:\n", + " x = [row[f\"{prefix}{start}/x\"], row[f\"{prefix}{end}/x\"]]\n", + " y = [row[f\"{prefix}{start}/y\"], row[f\"{prefix}{end}/y\"]]\n", + " z = [row[f\"{prefix}{start}/z\"], row[f\"{prefix}{end}/z\"]]\n", + " lines[i].set_data(x, y)\n", + " lines[i].set_3d_properties(z)\n", + " except KeyError:\n", + " continue\n", + " \n", + " # Update ball position\n", + " ball_point.set_data([row[\"ball/x\"]], [row[\"ball/y\"]])\n", + " ball_point.set_3d_properties([row[\"ball/z\"]])\n", + " \n", + " return lines + [ball_point]\n", + "\n", + " anim = FuncAnimation(fig, update, frames=num_frames, interval=40, blit=True)\n", + " plt.close(fig)\n", + " return anim\n", + "\n", + "# Animate player #10 for 6 seconds (150 frames at 25Hz)\n", + "anim = animate_player(container, \"10\", team=\"home\", start_frame=1500, num_frames=150)\n", + "HTML(anim.to_jshtml())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/07-catalog.ipynb b/examples/07-catalog.ipynb new file mode 100644 index 0000000..4312109 --- /dev/null +++ b/examples/07-catalog.ipynb @@ -0,0 +1,338 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TrackingDataCatalog: Iceberg-backed Storage\n", + "\n", + "This notebook demonstrates how to use the `TrackingDataCatalog` to manage the lifecycle of tracking data using **Apache Iceberg**. \n", + "\n", + "Iceberg provides several benefits for tracking data:\n", + "- **Scalability**: Handle thousands of matches in a single logical table.\n", + "- **Schema Evolution**: Add new metrics (e.g., speed, distance) without rewriting old data.\n", + "- **Pruning**: Efficiently query specific matches or players using partition and bucket pruning.\n", + "- **Atomic Commits**: Ensure data integrity with ACID transactions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import tracking_data_container as tdc\n", + "import pyarrow as pa\n", + "from datetime import datetime\n", + "from pathlib import Path\n", + "import shutil" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Setup the Catalog\n", + "\n", + "The catalog requires a `warehouse_path` where the data and metadata will be stored. We can also provide an `IcebergConfig` to tune optimization settings like bucketing and compression." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Catalog initialized at: /Users/probberechts/Developer/tracking_data_container/examples/example_warehouse\n" + ] + } + ], + "source": [ + "warehouse_path = \"./example_warehouse\"\n", + "if Path(warehouse_path).exists():\n", + " shutil.rmtree(warehouse_path)\n", + "\n", + "config = tdc.IcebergConfig(\n", + " compression=\"zstd\"\n", + ")\n", + "\n", + "catalog = tdc.TrackingDataCatalog(\n", + " warehouse_path=warehouse_path,\n", + " config=config\n", + ")\n", + "\n", + "# Initialize the Iceberg tables\n", + "catalog.initialize()\n", + "print(f\"Catalog initialized at: {catalog.warehouse_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Create and Save Tracking Data\n", + "\n", + "We'll create a sample container using the **Common Data Format (CDF)** hierarchical naming convention." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial container bound: False\n", + "Container saved to catalog.\n" + ] + } + ], + "source": [ + "data = {\n", + " \"match/id\": [\"match_A\"] * 5,\n", + " \"frame_id\": list(range(5)),\n", + " \"period\": [\"first_half\"] * 5,\n", + " \"timestamp\": [1000000 + i * 40000 for i in range(5)],\n", + " \"ball/x\": [0.0, 1.0, 2.0, 3.0, 4.0],\n", + " \"ball/y\": [0.0, 0.5, 1.0, 1.5, 2.0],\n", + "}\n", + "\n", + "# We use validate=False for synthetic data that doesn't follow the full CDF schema\n", + "container = tdc.TrackingDataContainer(data, validate=False)\n", + "print(f\"Initial container bound: {container.is_bound()}\")\n", + "\n", + "# Save the container to the catalog\n", + "catalog.save_container(container)\n", + "print(\"Container saved to catalog.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Load Data with Pruning\n", + "\n", + "When loading from the catalog, we can apply filters. Iceberg uses these filters to skip irrelevant files (pruning), making it extremely fast even for massive datasets.\n", + "\n", + "**Note**: `load_container` returns a `LazyTrackingDataContainer`. You must call `.collect()` to execute the plan and perform the wide-format pivot." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Is lazy: True\n", + "Loaded container bound: True\n", + "Rows: 5\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "shape: (5, 9)
competition/idseason/idmatch/idframe_idperiodtimestampball/xball/yball/z
strstrstri64catdatetime[μs]f32f32f32
nullnull"match_A"0"first_half"1970-01-01 00:00:010.00.0null
nullnull"match_A"1"first_half"1970-01-01 00:00:01.0401.00.5null
nullnull"match_A"2"first_half"1970-01-01 00:00:01.0802.01.0null
nullnull"match_A"3"first_half"1970-01-01 00:00:01.1203.01.5null
nullnull"match_A"4"first_half"1970-01-01 00:00:01.1604.02.0null
" + ], + "text/plain": [ + "shape: (5, 9)\n", + "┌────────────────┬───────────┬──────────┬──────────┬───┬────────────────┬────────┬────────┬────────┐\n", + "│ competition/id ┆ season/id ┆ match/id ┆ frame_id ┆ … ┆ timestamp ┆ ball/x ┆ ball/y ┆ ball/z │\n", + "│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │\n", + "│ str ┆ str ┆ str ┆ i64 ┆ ┆ datetime[μs] ┆ f32 ┆ f32 ┆ f32 │\n", + "╞════════════════╪═══════════╪══════════╪══════════╪═══╪════════════════╪════════╪════════╪════════╡\n", + "│ null ┆ null ┆ match_A ┆ 0 ┆ … ┆ 1970-01-01 ┆ 0.0 ┆ 0.0 ┆ null │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 1 ┆ … ┆ 1970-01-01 ┆ 1.0 ┆ 0.5 ┆ null │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.040 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 2 ┆ … ┆ 1970-01-01 ┆ 2.0 ┆ 1.0 ┆ null │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.080 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 3 ┆ … ┆ 1970-01-01 ┆ 3.0 ┆ 1.5 ┆ null │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.120 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 4 ┆ … ┆ 1970-01-01 ┆ 4.0 ┆ 2.0 ┆ null │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.160 ┆ ┆ ┆ │\n", + "└────────────────┴───────────┴──────────┴──────────┴───┴────────────────┴────────┴────────┴────────┘" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load specific match (returns a Lazy container)\n", + "lazy_loaded = catalog.load_container(match_id=\"match_A\")\n", + "\n", + "print(f\"Is lazy: {isinstance(lazy_loaded, tdc.LazyTrackingDataContainer)}\")\n", + "\n", + "# Execute the plan and pivot to wide format\n", + "loaded = lazy_loaded.collect()\n", + "\n", + "print(f\"Loaded container bound: {loaded.is_bound()}\")\n", + "print(f\"Rows: {loaded.num_rows()}\")\n", + "loaded.to_polars().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Modify and Save (Catalog Binding)\n", + "\n", + "A container loaded from a catalog is \"bound\" to it. This means we can perform transformations and then simply call `.save()` to commit the changes back to the warehouse." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Modified container bound: True\n", + "Changes committed to catalog.\n" + ] + } + ], + "source": [ + "# Add a new column (Schema Evolution)\n", + "# Note: ball/z is a standardized CDF field that maps to Float32\n", + "modified = loaded.add_column(\"ball/z\", [0.5] * loaded.num_rows())\n", + "\n", + "print(f\"Modified container bound: {modified.is_bound()}\")\n", + "\n", + "# Atomic commit back to Iceberg\n", + "modified.save()\n", + "print(\"Changes committed to catalog.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Verify Schema Evolution\n", + "\n", + "We can now load the data again and see that the new column is present." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "shape: (5, 9)
competition/idseason/idmatch/idframe_idperiodtimestampball/xball/yball/z
strstrstri64catdatetime[μs]f32f32f32
nullnull"match_A"0"first_half"1970-01-01 00:00:010.00.00.5
nullnull"match_A"1"first_half"1970-01-01 00:00:01.0401.00.50.5
nullnull"match_A"2"first_half"1970-01-01 00:00:01.0802.01.00.5
nullnull"match_A"3"first_half"1970-01-01 00:00:01.1203.01.50.5
nullnull"match_A"4"first_half"1970-01-01 00:00:01.1604.02.00.5
" + ], + "text/plain": [ + "shape: (5, 9)\n", + "┌────────────────┬───────────┬──────────┬──────────┬───┬────────────────┬────────┬────────┬────────┐\n", + "│ competition/id ┆ season/id ┆ match/id ┆ frame_id ┆ … ┆ timestamp ┆ ball/x ┆ ball/y ┆ ball/z │\n", + "│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │\n", + "│ str ┆ str ┆ str ┆ i64 ┆ ┆ datetime[μs] ┆ f32 ┆ f32 ┆ f32 │\n", + "╞════════════════╪═══════════╪══════════╪══════════╪═══╪════════════════╪════════╪════════╪════════╡\n", + "│ null ┆ null ┆ match_A ┆ 0 ┆ … ┆ 1970-01-01 ┆ 0.0 ┆ 0.0 ┆ 0.5 │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 1 ┆ … ┆ 1970-01-01 ┆ 1.0 ┆ 0.5 ┆ 0.5 │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.040 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 2 ┆ … ┆ 1970-01-01 ┆ 2.0 ┆ 1.0 ┆ 0.5 │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.080 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 3 ┆ … ┆ 1970-01-01 ┆ 3.0 ┆ 1.5 ┆ 0.5 │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.120 ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ match_A ┆ 4 ┆ … ┆ 1970-01-01 ┆ 4.0 ┆ 2.0 ┆ 0.5 │\n", + "│ ┆ ┆ ┆ ┆ ┆ 00:00:01.160 ┆ ┆ ┆ │\n", + "└────────────────┴───────────┴──────────┴──────────┴───┴────────────────┴────────┴────────┴────────┘" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final = catalog.load_container(match_id=\"match_A\").collect()\n", + "final.to_polars().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Cleanup\n", + "\n", + "Remove the example warehouse." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warehouse cleaned up.\n" + ] + } + ], + "source": [ + "if Path(warehouse_path).exists():\n", + " shutil.rmtree(warehouse_path)\n", + "print(\"Warehouse cleaned up.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/08-idsse-warehouse.ipynb b/examples/08-idsse-warehouse.ipynb new file mode 100644 index 0000000..1e3a332 --- /dev/null +++ b/examples/08-idsse-warehouse.ipynb @@ -0,0 +1,445 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-Match Warehouse with IDSSE Dataset\n", + "\n", + "This notebook demonstrates building a multi-match tracking data warehouse using the **IDSSE dataset**. \n", + "\n", + "We will:\n", + "1. Discover multiple matches in the local `data` directory.\n", + "2. Parse the Sportec (DFL) XML files using TDC's high-performance Rust parser.\n", + "3. Ingest all matches into a single Iceberg table using a scalable hybrid format.\n", + "4. Demonstrate efficient cross-match lazy querying." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import tracking_data_container as tdc\n", + "import pyarrow as pa\n", + "import polars as pl\n", + "from pathlib import Path\n", + "import shutil\n", + "import time\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Discover Data\n", + "\n", + "We use the `examples/data` directory which contains professional matches in Sportec format." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 7 matches to ingest.\n", + " - DFL-MAT-J03WMX\n", + " - DFL-MAT-J03WN1\n", + " - DFL-MAT-J03WOH\n", + " - DFL-MAT-J03WOY\n", + " - DFL-MAT-J03WPY\n", + " - DFL-MAT-J03WQQ\n", + " - DFL-MAT-J03WR9\n" + ] + } + ], + "source": [ + "data_dir = Path(\"data\")\n", + "if not data_dir.exists():\n", + " # Fallback for if we are running from the root instead of examples/\n", + " data_dir = Path(\"examples/data\")\n", + "\n", + "all_files = os.listdir(data_dir)\n", + "\n", + "# Pair position files with metadata files\n", + "pos_files = sorted([f for f in all_files if \"positions_raw_observed\" in f])\n", + "meta_files = sorted([f for f in all_files if \"matchinformation\" in f])\n", + "\n", + "pairs = []\n", + "for pos in pos_files:\n", + " # Extract match ID (the last part of the filename)\n", + " match_id = pos.split(\"_\")[-1].replace(\".xml\", \"\")\n", + " meta = next((m for m in meta_files if match_id in m), None)\n", + " if meta:\n", + " pairs.append({\"match_id\": match_id, \"pos\": pos, \"meta\": meta})\n", + "\n", + "print(f\"Found {len(pairs)} matches to ingest.\")\n", + "for p in pairs:\n", + " print(f\" - {p['match_id']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Setup Warehouse\n", + "\n", + "We initialize our `TrackingDataCatalog`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warehouse initialized at ./idsse_warehouse\n" + ] + } + ], + "source": [ + "warehouse_path = \"./idsse_warehouse\"\n", + "if Path(warehouse_path).exists():\n", + " shutil.rmtree(warehouse_path)\n", + "\n", + "catalog = tdc.TrackingDataCatalog(warehouse_path=warehouse_path)\n", + "catalog.initialize()\n", + "print(f\"Warehouse initialized at {warehouse_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Ingest Matches\n", + "\n", + "For each match, we parse the raw XML using the optimized Rust backend and save it to the Iceberg table. \n", + "\n", + "**Hybrid Approach**: The container is wide-format (one column per player), but the catalog automatically \"melts\" it into a fixed long-format schema for storage." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Processing match DFL-MAT-J03WMX...\n", + " ✅ Parsed in 0.90s, Saved in 0.22s\n", + " ✅ Rows: 7,500\n", + "\n", + "Processing match DFL-MAT-J03WN1...\n", + " ✅ Parsed in 0.92s, Saved in 0.17s\n", + " ✅ Rows: 7,500\n", + "\n", + "Processing match DFL-MAT-J03WOH...\n", + " ✅ Parsed in 0.75s, Saved in 0.18s\n", + " ✅ Rows: 7,500\n" + ] + } + ], + "source": [ + "for pair in pairs[:3]: # Let's just do 3 for speed in this example\n", + " match_id = pair['match_id']\n", + " print(f\"\\nProcessing match {match_id}...\")\n", + " \n", + " pos_path = data_dir / pair['pos']\n", + " meta_path = data_dir / pair['meta']\n", + " \n", + " # Parse (High-Performance Rust)\n", + " start = time.perf_counter()\n", + " container = tdc.load_sportec(str(pos_path), str(meta_path))\n", + " parse_time = time.perf_counter() - start\n", + " \n", + " # Add match ID column (required for partitioning)\n", + " container = container.add_column(\"match/id\", [match_id] * container.num_rows())\n", + " \n", + " # Save to Warehouse (melts to long format internally)\n", + " start = time.perf_counter()\n", + " catalog.save_container(container)\n", + " save_time = time.perf_counter() - start\n", + " \n", + " print(f\" ✅ Parsed in {parse_time:.2f}s, Saved in {save_time:.2f}s\")\n", + " print(f\" ✅ Rows: {container.num_rows():,}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Querying the Warehouse\n", + "\n", + "Now that our data is stored in Iceberg, we can perform efficient queries. \n", + "\n", + "### Count rows per match\n", + "Loading is instantaneous because it is **Lazy**. The data is only pivoted back to the wide format when you call `.collect()`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warehouse Summary:\n", + " - Match DFL-MAT-J03WMX: 7,500 rows\n", + " - Match DFL-MAT-J03WN1: 7,500 rows\n", + " - Match DFL-MAT-J03WOH: 7,500 rows\n", + "\n", + "Total rows in warehouse: 22,500\n" + ] + } + ], + "source": [ + "print(\"Warehouse Summary:\")\n", + "total_rows = 0\n", + "for pair in pairs[:3]:\n", + " m_id = pair['match_id']\n", + " # returns a LazyTrackingDataContainer\n", + " lazy_container = catalog.load_container(match_id=m_id)\n", + " \n", + " # Execute plan and pivot to wide format\n", + " loaded = lazy_container.collect()\n", + " \n", + " rows = loaded.num_rows()\n", + " total_rows += rows\n", + " print(f\" - Match {m_id}: {rows:,} rows\")\n", + "\n", + "print(f\"\\nTotal rows in warehouse: {total_rows:,}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load specific player\n", + "We can load data for a specific match and see the wide-format columns automatically reconstructed." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading data for DFL-MAT-J03WMX...\n", + " ✅ Loaded and pivoted 7500 rows in 0.0779s\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "shape: (5, 181)
competition/idseason/idmatch/idframe_idperiodtimestampball_poss_team_idball_statusteams/none/players/DFL-OBJ-0002BO/player/idteams/none/players/DFL-OBJ-0002BO/xteams/none/players/DFL-OBJ-0002BO/yteams/none/players/DFL-OBJ-0002BO/zteams/none/players/DFL-OBJ-0002BO/accteams/none/players/DFL-OBJ-0002BO/velteams/none/players/DFL-OBJ-J017RE/player/idteams/none/players/DFL-OBJ-J017RE/xteams/none/players/DFL-OBJ-J017RE/yteams/none/players/DFL-OBJ-J017RE/zteams/none/players/DFL-OBJ-J017RE/accteams/none/players/DFL-OBJ-J017RE/velteams/none/players/DFL-OBJ-J01APO/player/idteams/none/players/DFL-OBJ-J01APO/xteams/none/players/DFL-OBJ-J01APO/yteams/none/players/DFL-OBJ-J01APO/zteams/none/players/DFL-OBJ-J01APO/accteams/none/players/DFL-OBJ-J01APO/velteams/home/player/idteams/home/xteams/home/yteams/home/zteams/home/accteams/home/velteams/none/players/DFL-OBJ-0000IA/player/idteams/none/players/DFL-OBJ-0000IA/xteams/none/players/DFL-OBJ-0000IA/yteams/none/players/DFL-OBJ-0000IA/zteams/none/players/DFL-OBJ-0000IA/accteams/none/players/DFL-OBJ-0027AX/velteams/none/players/DFL-OBJ-0027G6/player/idteams/none/players/DFL-OBJ-0027G6/xteams/none/players/DFL-OBJ-0027G6/yteams/none/players/DFL-OBJ-0027G6/zteams/none/players/DFL-OBJ-0027G6/accteams/none/players/DFL-OBJ-0027G6/velteams/none/players/DFL-OBJ-0002DR/player/idteams/none/players/DFL-OBJ-0002DR/xteams/none/players/DFL-OBJ-0002DR/yteams/none/players/DFL-OBJ-0002DR/zteams/none/players/DFL-OBJ-0002DR/accteams/none/players/DFL-OBJ-0002DR/velreferees/DFL-OBJ-000141/player/idreferees/DFL-OBJ-000141/xreferees/DFL-OBJ-000141/yreferees/DFL-OBJ-000141/zreferees/DFL-OBJ-000141/accreferees/DFL-OBJ-000141/velteams/none/players/DFL-OBJ-J01BGM/player/idteams/none/players/DFL-OBJ-J01BGM/xteams/none/players/DFL-OBJ-J01BGM/yteams/none/players/DFL-OBJ-J01BGM/zteams/none/players/DFL-OBJ-J01BGM/accteams/none/players/DFL-OBJ-J01BGM/velteams/none/players/DFL-OBJ-0027G0/player/idteams/none/players/DFL-OBJ-0027G0/xteams/none/players/DFL-OBJ-0027G0/yteams/none/players/DFL-OBJ-0027G0/zteams/none/players/DFL-OBJ-0027G0/accteams/none/players/DFL-OBJ-0027G0/velteams/home/jersey_colourteams/home/nameteams/home/idteams/away/jersey_colourteams/away/nameteams/away/id
strstrstri64catdatetime[μs]strboolstrf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strf32f32f32f64f64strstrstrstrstrstr
nullnull"DFL-MAT-J03WMX"10000"first_half"2023-05-27 13:30:12.600"DFL-CLU-00000G"null"DFL-OBJ-0002BO"17.2620.85null0.00.0"DFL-OBJ-J017RE"-20.779.07null0.00.0"DFL-OBJ-J01APO"-13.825.719999null0.00.0"team:home"nullnullnullnullnull"DFL-OBJ-0000IA"-1.5312.68null0.00.0"DFL-OBJ-0027G6"0.78-0.54null0.00.0"DFL-OBJ-0002DR"-46.730.18null0.00.0"referee:DFL-OBJ-000141"1.218.15null0.00.0"DFL-OBJ-J01BGM"1.413.05null0.00.0"DFL-OBJ-0027G0"-12.8-18.85null0.00.0"#F4F5F0""1. FC Köln""DFL-CLU-000008""#25282A""FC Bayern München""DFL-CLU-00000G"
nullnull"DFL-MAT-J03WMX"10001"first_half"2023-05-27 13:30:12.640"DFL-CLU-00000G"null"DFL-OBJ-0002BO"17.2320.879999null1.084.43"DFL-OBJ-J017RE"-20.779.11null2.113.1"DFL-OBJ-J01APO"-13.7825.74null1.232.59"team:home"nullnullnullnullnull"DFL-OBJ-0000IA"-1.4512.68null0.08.94"DFL-OBJ-0027G6"0.85-0.56null2.347.74"DFL-OBJ-0002DR"-46.7099990.18null1.012.21"referee:DFL-OBJ-000141"1.2818.129999null0.676.96"DFL-OBJ-J01BGM"1.3913.05null0.150.7"DFL-OBJ-0027G0"-12.83-18.860001null0.03.04"#F4F5F0""1. FC Köln""DFL-CLU-000008""#25282A""FC Bayern München""DFL-CLU-00000G"
nullnull"DFL-MAT-J03WMX"10002"first_half"2023-05-27 13:30:12.680"DFL-CLU-00000G"null"DFL-OBJ-0002BO"17.1820.91null1.074.6"DFL-OBJ-J017RE"-20.779.14null2.063.4"DFL-OBJ-J01APO"-13.7525.76null1.082.76"team:home"nullnullnullnullnull"DFL-OBJ-0000IA"-1.2712.65null0.09.39"DFL-OBJ-0027G6"0.94-0.59null2.288.08"DFL-OBJ-0002DR"-46.680.18null0.982.35"referee:DFL-OBJ-000141"1.3418.09null0.617.05"DFL-OBJ-J01BGM"1.3913.05null0.670.76"DFL-OBJ-0027G0"-12.88-18.860001null0.03.17"#F4F5F0""1. FC Köln""DFL-CLU-000008""#25282A""FC Bayern München""DFL-CLU-00000G"
nullnull"DFL-MAT-J03WMX"10003"first_half"2023-05-27 13:30:12.720"DFL-CLU-00000G"null"DFL-OBJ-0002BO"17.1520.940001null1.034.74"DFL-OBJ-J017RE"-20.779.19null2.03.7"DFL-OBJ-J01APO"-13.7325.780001null0.912.89"team:home"nullnullnullnullnull"DFL-OBJ-0000IA"-1.0712.64null0.09.82"DFL-OBJ-0027G6"1.03-0.62null2.268.4"DFL-OBJ-0002DR"-46.6500020.18null0.962.49"referee:DFL-OBJ-000141"1.4118.059999null0.557.14"DFL-OBJ-J01BGM"1.3813.06null1.190.9"DFL-OBJ-0027G0"-12.91-18.870001null0.03.31"#F4F5F0""1. FC Köln""DFL-CLU-000008""#25282A""FC Bayern München""DFL-CLU-00000G"
nullnull"DFL-MAT-J03WMX"10004"first_half"2023-05-27 13:30:12.760"DFL-CLU-00000G"null"DFL-OBJ-0002BO"17.11000120.98null0.984.88"DFL-OBJ-J017RE"-20.7800019.22null1.923.98"DFL-OBJ-J01APO"-13.7125.809999null0.723.0"team:home"nullnullnullnullnull"DFL-OBJ-0000IA"-0.8812.61null0.010.21"DFL-OBJ-0027G6"1.12-0.65null2.298.74"DFL-OBJ-0002DR"-46.6199990.18null0.952.63"referee:DFL-OBJ-000141"1.4918.02null0.477.2"DFL-OBJ-J01BGM"1.3713.06null1.671.11"DFL-OBJ-0027G0"-12.94-18.870001null0.03.47"#F4F5F0""1. FC Köln""DFL-CLU-000008""#25282A""FC Bayern München""DFL-CLU-00000G"
" + ], + "text/plain": [ + "shape: (5, 181)\n", + "┌───────────┬───────────┬───────────┬──────────┬───┬───────────┬───────────┬───────────┬───────────┐\n", + "│ competiti ┆ season/id ┆ match/id ┆ frame_id ┆ … ┆ teams/hom ┆ teams/awa ┆ teams/awa ┆ teams/awa │\n", + "│ on/id ┆ --- ┆ --- ┆ --- ┆ ┆ e/id ┆ y/jersey_ ┆ y/name ┆ y/id │\n", + "│ --- ┆ str ┆ str ┆ i64 ┆ ┆ --- ┆ colour ┆ --- ┆ --- │\n", + "│ str ┆ ┆ ┆ ┆ ┆ str ┆ --- ┆ str ┆ str │\n", + "│ ┆ ┆ ┆ ┆ ┆ ┆ str ┆ ┆ │\n", + "╞═══════════╪═══════════╪═══════════╪══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡\n", + "│ null ┆ null ┆ DFL-MAT-J ┆ 10000 ┆ … ┆ DFL-CLU-0 ┆ #25282A ┆ FC Bayern ┆ DFL-CLU-0 │\n", + "│ ┆ ┆ 03WMX ┆ ┆ ┆ 00008 ┆ ┆ München ┆ 0000G │\n", + "│ null ┆ null ┆ DFL-MAT-J ┆ 10001 ┆ … ┆ DFL-CLU-0 ┆ #25282A ┆ FC Bayern ┆ DFL-CLU-0 │\n", + "│ ┆ ┆ 03WMX ┆ ┆ ┆ 00008 ┆ ┆ München ┆ 0000G │\n", + "│ null ┆ null ┆ DFL-MAT-J ┆ 10002 ┆ … ┆ DFL-CLU-0 ┆ #25282A ┆ FC Bayern ┆ DFL-CLU-0 │\n", + "│ ┆ ┆ 03WMX ┆ ┆ ┆ 00008 ┆ ┆ München ┆ 0000G │\n", + "│ null ┆ null ┆ DFL-MAT-J ┆ 10003 ┆ … ┆ DFL-CLU-0 ┆ #25282A ┆ FC Bayern ┆ DFL-CLU-0 │\n", + "│ ┆ ┆ 03WMX ┆ ┆ ┆ 00008 ┆ ┆ München ┆ 0000G │\n", + "│ null ┆ null ┆ DFL-MAT-J ┆ 10004 ┆ … ┆ DFL-CLU-0 ┆ #25282A ┆ FC Bayern ┆ DFL-CLU-0 │\n", + "│ ┆ ┆ 03WMX ┆ ┆ ┆ 00008 ┆ ┆ München ┆ 0000G │\n", + "└───────────┴───────────┴───────────┴──────────┴───┴───────────┴───────────┴───────────┴───────────┘" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "target_match = pairs[0]['match_id']\n", + "\n", + "print(f\"Loading data for {target_match}...\")\n", + "start = time.perf_counter()\n", + "\n", + "# Load specific match lazily and collect\n", + "container = catalog.load_container(match_id=target_match).collect()\n", + "\n", + "query_time = time.perf_counter() - start\n", + "print(f\" ✅ Loaded and pivoted {container.num_rows()} rows in {query_time:.4f}s\")\n", + "container.to_polars().head()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'_entities': {'ball': {},\n", + " 'teams': {'home': {'players': {},\n", + " 'jersey_colour': '#F4F5F0',\n", + " 'name': '1. FC Köln',\n", + " 'id': 'DFL-CLU-000008'},\n", + " 'away': {'players': {},\n", + " 'jersey_colour': '#25282A',\n", + " 'name': 'FC Bayern München',\n", + " 'id': 'DFL-CLU-00000G'}},\n", + " 'referees': {}},\n", + " '_schema_id': 'cdf/tracking/0.2.3'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "metadata = catalog.get_metadata(match_id=target_match)\n", + "metadata" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ✅ Loaded 607500 rows in 0.0332s\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "shape: (5, 16)
competition_idseason_idmatch_idframe_idperiodtimestampentity_typeentity_idxyzball_poss_team_idball_statusentity_keyaccvel
strstrstri64strdatetime[μs]strstrf32f32f32strboolstrf64f64
nullnull"DFL-MAT-J03WN1"10000"first_half"2023-05-27 13:31:06.400"team""team:away"nullnullnull"DFL-CLU-00000S"true"team:away"nullnull
nullnull"DFL-MAT-J03WN1"10001"first_half"2023-05-27 13:31:06.440"team""team:away"nullnullnull"DFL-CLU-00000S"true"team:away"nullnull
nullnull"DFL-MAT-J03WN1"10002"first_half"2023-05-27 13:31:06.480"team""team:away"nullnullnull"DFL-CLU-00000S"true"team:away"nullnull
nullnull"DFL-MAT-J03WN1"10003"first_half"2023-05-27 13:31:06.520"team""team:away"nullnullnull"DFL-CLU-00000S"true"team:away"nullnull
nullnull"DFL-MAT-J03WN1"10004"first_half"2023-05-27 13:31:06.560"team""team:away"nullnullnull"DFL-CLU-00000S"true"team:away"nullnull
" + ], + "text/plain": [ + "shape: (5, 16)\n", + "┌───────────────┬───────────┬──────────────┬──────────┬───┬─────────────┬────────────┬──────┬──────┐\n", + "│ competition_i ┆ season_id ┆ match_id ┆ frame_id ┆ … ┆ ball_status ┆ entity_key ┆ acc ┆ vel │\n", + "│ d ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │\n", + "│ --- ┆ str ┆ str ┆ i64 ┆ ┆ bool ┆ str ┆ f64 ┆ f64 │\n", + "│ str ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "╞═══════════════╪═══════════╪══════════════╪══════════╪═══╪═════════════╪════════════╪══════╪══════╡\n", + "│ null ┆ null ┆ DFL-MAT-J03W ┆ 10000 ┆ … ┆ true ┆ team:away ┆ null ┆ null │\n", + "│ ┆ ┆ N1 ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ DFL-MAT-J03W ┆ 10001 ┆ … ┆ true ┆ team:away ┆ null ┆ null │\n", + "│ ┆ ┆ N1 ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ DFL-MAT-J03W ┆ 10002 ┆ … ┆ true ┆ team:away ┆ null ┆ null │\n", + "│ ┆ ┆ N1 ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ DFL-MAT-J03W ┆ 10003 ┆ … ┆ true ┆ team:away ┆ null ┆ null │\n", + "│ ┆ ┆ N1 ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "│ null ┆ null ┆ DFL-MAT-J03W ┆ 10004 ┆ … ┆ true ┆ team:away ┆ null ┆ null │\n", + "│ ┆ ┆ N1 ┆ ┆ ┆ ┆ ┆ ┆ │\n", + "└───────────────┴───────────┴──────────────┴──────────┴───┴─────────────┴────────────┴──────┴──────┘" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "start = time.perf_counter()\n", + "\n", + "import glob\n", + "parquet_files = glob.glob(\n", + " os.path.join(warehouse_path, \"tracking\", \"tracking_data\", \"**\", \"*.parquet\"),\n", + " recursive=True,\n", + ")\n", + "\n", + "df = pl.scan_parquet(parquet_files).collect()\n", + "query_time = time.perf_counter() - start\n", + "print(f\" ✅ Loaded {len(df)} rows in {query_time:.4f}s\")\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Cleanup" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warehouse cleaned up.\n" + ] + } + ], + "source": [ + "if Path(warehouse_path).exists():\n", + " shutil.rmtree(warehouse_path)\n", + "print(\"Warehouse cleaned up.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "/Users/probberechts/Developer/tracking_data_container", + "language": "python", + "name": "tracking_data_container" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/20260423-presentation-liss/build.sh b/examples/20260423-presentation-liss/build.sh new file mode 100755 index 0000000..85e7957 --- /dev/null +++ b/examples/20260423-presentation-liss/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Build and serve the presentation +# This will generate presentation.slides.html and open it in your browser. + +jupyter nbconvert presentation.ipynb \ + --to slides \ + --RevealHelpTransformer.url_prefix=https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2 \ + --RevealExporter.theme=simple \ + --RevealExporter.transition=slide \ + --post serve diff --git a/examples/20260423-presentation-liss/custom.css b/examples/20260423-presentation-liss/custom.css new file mode 100644 index 0000000..33fd174 --- /dev/null +++ b/examples/20260423-presentation-liss/custom.css @@ -0,0 +1,51 @@ +/* General Slide Styling */ +.reveal h1 { + font-size: 2.5em !important; + color: #1a73e8 !important; + text-transform: none !important; +} + +.reveal h2 { + font-size: 1.8em !important; + color: #444 !important; + border-bottom: 2px solid #1a73e8; + padding-bottom: 10px; + text-transform: none !important; +} + +.reveal p, .reveal li { + font-size: 1.2em !important; + line-height: 1.4em !important; +} + +/* Code Styling */ +.reveal pre code { + font-size: 1.1em !important; + line-height: 1.2em !important; + border-radius: 8px !important; + padding: 20px !important; +} + +.reveal .rendered_html table { + font-size: 0.8em !important; +} + +/* Layout Adjustments */ +.reveal .slides { + text-align: left !important; +} + +.reveal .progress { + color: #1a73e8 !important; +} + +/* RISE specific tweaks */ +.rise-enabled .reveal .slides { + font-size: 120%; /* Scale up everything for RISE */ +} + +/* Ensure images don't overflow */ +.reveal img { + max-height: 500px !important; + width: auto !important; +} diff --git a/examples/20260423-presentation-liss/img/building_bridges.jpeg b/examples/20260423-presentation-liss/img/building_bridges.jpeg new file mode 100644 index 0000000..866fc94 Binary files /dev/null and b/examples/20260423-presentation-liss/img/building_bridges.jpeg differ diff --git a/examples/20260423-presentation-liss/img/coordinatefc.png b/examples/20260423-presentation-liss/img/coordinatefc.png new file mode 100644 index 0000000..94c5426 Binary files /dev/null and b/examples/20260423-presentation-liss/img/coordinatefc.png differ diff --git a/examples/20260423-presentation-liss/img/pysport_logo.png b/examples/20260423-presentation-liss/img/pysport_logo.png new file mode 100644 index 0000000..2987818 Binary files /dev/null and b/examples/20260423-presentation-liss/img/pysport_logo.png differ diff --git a/examples/20260423-presentation-liss/presentation.ipynb b/examples/20260423-presentation-liss/presentation.ipynb new file mode 100644 index 0000000..8a2f62b --- /dev/null +++ b/examples/20260423-presentation-liss/presentation.ipynb @@ -0,0 +1,443 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "\n", + "\n", + "*(This cell links custom CSS for the presentation and is hidden during the show)*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "# RFC: Tracking Data Container\n", + "### A Metadata-Aware High-Performance Container for Sports Tracking Data\n", + "\n", + "**Pieter Robberechts, Koen Vossen, et al.**\n", + "\n", + "April 2026\n", + "\n", + "\"logo\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## The Fragmentation of Open-Source\n", + "\n", + "People are building great open source packages but there is little to no coordination and therefore no interoperability. \n", + "\n", + "\n", + "\n", + "See " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "jp-MarkdownHeadingCollapsed": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "Lots of time is wasted on transforming output from one package into input for another one. This can be solved by a common standard. This increases interoperability and saves you time.\n", + "\n", + "Sports analytics is currently suffering from **duplicated effort** across various open-source packages.\n", + "\n", + "- **Parsing & Modeling**: Kloppy, Floodlight, and others often build similar logic.\n", + "- **The Scalability Wall**: Object-based models (like Kloppy) are excellent for small datasets but fail when processing millions of rows of tracking data.\n", + "- **Inconsistent Schemas**: Every tool uses different column names, making interoperability a nightmare.\n", + "- Lots of time is wasted on transforming output from one package into input for another one. This can be solved by a common standard. \n", + "\n", + "- Lots of implemetation effort is wasted by re-implementing data parsers across various open-source packages. \n", + "\n", + "\n", + "| | databallpy | PreProcessing/Event | floodlight | gandula | soccercpd | defcon | ballradar | socceraction | soccer-xg | unravelsports | kloppy | ingestify |\n", + "|------------------|------------|---------------------|---------------|----------|-----------|----------|-----------|--------------|-----------|---------------|----------|-----------|\n", + "| Ingestion | | | | | | | | | | | | ✔️ |\n", + "| Data management | | | | | | | | | | | | ✔️ |\n", + "| Parsing Events | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | |\n", + "| Parsing Tracking | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |\n", + "| Event Sync | ✔️ | | | | | ✔️ | | | | | | |\n", + "| Metrics | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | |\n", + "| Visuals | ✔️ | | | ✔️ | | | ✔️ | ✔️ | | | | |\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## 🌉🌁 𝐁𝐮𝐢𝐥𝐝𝐢𝐧𝐠 𝐛𝐫𝐢𝐝𝐠𝐞𝐬! \n", + "\n", + "We brought together contributors from different open source projects to discuss how we can align our work and improve interoperability in football analytics. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "source": [ + "Socceraction v1.5.0 added support for loading data using kloppy." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/probberechts/Developer/tracking_data_container/.venv/lib/python3.11/site-packages/kloppy/_providers/statsbomb.py:86: UserWarning: \n", + "\n", + "You are about to use StatsBomb public data.\n", + "By using this data, you are agreeing to the user agreement. \n", + "The user agreement can be found here: https://github.com/statsbomb/open-data/blob/master/LICENSE.pdf\n", + "\n", + " warnings.warn(\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "FIXED_HOME_AWAY", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 5\u001b[39m\n\u001b[32m 2\u001b[39m dataset = statsbomb.load_open_data(match_id=\u001b[32m8657\u001b[39m)\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msocceraction\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mspadl\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mkloppy\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m convert_to_actions\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m spadl_actions = \u001b[43mconvert_to_actions\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdataset\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgame_id\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m8657\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 7\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msocceraction\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01matomic\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mspadl\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01matomicspadl\u001b[39;00m\n\u001b[32m 8\u001b[39m atomic_spadl_actions = atomicspadl.convert_to_atomic(spadl_actions)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Developer/tracking_data_container/.venv/lib/python3.11/site-packages/socceraction/spadl/kloppy.py:90\u001b[39m, in \u001b[36mconvert_to_actions\u001b[39m\u001b[34m(dataset, game_id)\u001b[39m\n\u001b[32m 82\u001b[39m warnings.warn(\n\u001b[32m 83\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mConverting \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdataset.metadata.provider\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m data is only supported from \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 84\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mKloppy version \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m_SUPPORTED_PROVIDERS[dataset.metadata.provider]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m (you have \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m_KLOPPY_VERSION\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m). \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 85\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mThe result may be incorrect or incomplete.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 86\u001b[39m )\n\u001b[32m 88\u001b[39m \u001b[38;5;66;03m# Convert the dataset to the SPADL coordinate system\u001b[39;00m\n\u001b[32m 89\u001b[39m new_dataset = dataset.transform(\n\u001b[32m---> \u001b[39m\u001b[32m90\u001b[39m to_orientation=\u001b[43mOrientation\u001b[49m\u001b[43m.\u001b[49m\u001b[43mFIXED_HOME_AWAY\u001b[49m, \u001b[38;5;66;03m# FIXME\u001b[39;00m\n\u001b[32m 91\u001b[39m to_coordinate_system=_SoccerActionCoordinateSystem(normalized=\u001b[38;5;28;01mFalse\u001b[39;00m),\n\u001b[32m 92\u001b[39m )\n\u001b[32m 94\u001b[39m \u001b[38;5;66;03m# Convert the events to SPADL actions\u001b[39;00m\n\u001b[32m 95\u001b[39m actions = []\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/share/uv/python/cpython-3.11.14-macos-aarch64-none/lib/python3.11/enum.py:786\u001b[39m, in \u001b[36mEnumType.__getattr__\u001b[39m\u001b[34m(cls, name)\u001b[39m\n\u001b[32m 784\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mcls\u001b[39m._member_map_[name]\n\u001b[32m 785\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m786\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(name) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n", + "\u001b[31mAttributeError\u001b[39m: FIXED_HOME_AWAY" + ] + } + ], + "source": [ + "from kloppy import statsbomb\n", + "dataset = statsbomb.load_open_data(match_id=8657)\n", + "\n", + "from socceraction.spadl.kloppy import convert_to_actions\n", + "spadl_actions = convert_to_actions(dataset, game_id=8657)\n", + "\n", + "import socceraction.atomic.spadl as atomicspadl\n", + "atomic_spadl_actions = atomicspadl.convert_to_atomic(spadl_actions)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "source": [ + "Databallpy added support for loading data using kloppy." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Succesfully loaded databallpy.game.Game object: Fortuna Düsseldorf 0 - 1 1. FC Nürnberg\n" + ] + } + ], + "source": [ + "# create a Kloppy TrackingDataset and/or EventDataset\n", + "from kloppy import sportec\n", + "\n", + "event_dataset = sportec.load_open_event_data(match_id=\"J03WPY\")\n", + "tracking_dataset = sportec.load_open_tracking_data(match_id=\"J03WPY\", only_alive=False)\n", + "\n", + "# transform the kloppy dataset to a databallpy game object:\n", + "from databallpy import get_game_from_kloppy\n", + "\n", + "game = get_game_from_kloppy(tracking_dataset=tracking_dataset,event_dataset=event_dataset)\n", + "print(f\"Succesfully loaded {game}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## Is a Dataframe Agreement Enough?\n", + "\n", + "A common suggestion is: *\"Why don't we just all use Polars and agree on column names?\"*\n", + "\n", + "We believe this is **insufficient** because:\n", + "- **Metadata Loss**: Dataframes lose the context of coordinate systems, field sizes, and team orientations.\n", + "- **Semantic Fragility**: Column names like `x_1` or `home_7_y` are fragile and lack domain-specific logic.\n", + "- **Logic Duplication**: Operations like coordinate normalization shouldn't be re-implemented for every project." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## The Proposed Solution: TDC\n", + "\n", + "The **Tracking Data Container** is a shared, standardized, metadata-aware interface.\n", + "\n", + "- **Implemented in Rust**: Fast.\n", + "- **Backed by Apache Arrow**: Zero-copy, memory-efficient.\n", + "- **(Soft) Enforced Metadata**: The container *knows* what it's holding.\n", + "- **Shared CDF Schema**: Guaranteed interoperability across the ecosystem." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## Why Rust, Arrow & CDF?\n", + "\n", + "### Apache Arrow\n", + "- A community-driven standard for columnar in-memory data.\n", + "- Enables **zero-copy** transfer between Rust and Python." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### Common Data Format (CDF)\n", + "- We align with the **CDF v0.2.2** standard for hierarchical naming.\n", + "- `ball/x`, `teams/{tid}/players/{pid}/y`\n", + "- This ensures compatibility with research and federation-level tooling." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## 5. Design & Functionalities\n", + "\n", + "- **TrackingDataContainer**: The eager interface for interactive work.\n", + "- **DataField**: Domain-specific representation of a column.\n", + "- **ColumnSelector**: Metadata-aware querying (e.g., `selector.players('home').x`).\n", + "- **CoordinateSystem**: Built-in support for transformations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "import tracking_data_container as tdc\n", + "from pathlib import Path\n", + "\n", + "# Load and transform in 3 lines\n", + "container = tdc.load_sportec(\"../../data/sportec/tracking.xml\")\n", + "target_cs = tdc.CoordinateSystem(x_range=(-52.5, 52.5), y_range=(-34, 34), origin=\"center\")\n", + "container = container.transform_coordinates(target_cs)\n", + "\n", + "# Metadata-aware selection\n", + "home_x = container.select(container.selector.players(team_id=\"home\").attributes([\"x\"]))\n", + "home_x.to_polars().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## 6. Advanced: Lazy Eval & Filter Pushdown\n", + "\n", + "For high-cardinality data like **Pose Tracking (Skeletal)**, eager loading is impossible.\n", + "\n", + "- **LazyTrackingDataContainer**: Uses DataFusion logical plans.\n", + "- **Filter Pushdown**: Queries like `filter(period == 1)` skip reading irrelevant data from disk.\n", + "- **Performance**: Scales to hundreds of joints across multiple players." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "# Lazy load Hawkeye skeletal data\n", + "H_DIR = Path(\"../../data/hawkeye\")\n", + "hawkeye = tdc.load_hawkeye(\n", + " list(H_DIR.glob(\"*.ball.json\"))[:5], \n", + " list(H_DIR.glob(\"*.centroids.json\"))[:5], \n", + " joint_paths=list(H_DIR.glob(\"*.joints.json\"))[:5], \n", + " meta_path=H_DIR / \"meta.json\"\n", + ").lazy()\n", + "\n", + "# Pushdown: Only process what we need\n", + "subset = hawkeye.filter(tdc.col(\"period\") == 1).select([\"timestamp\", \"ball/x\", \"ball/y\"])\n", + "subset.collect().to_polars().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## 7. Warehouse Scale: Apache Iceberg\n", + "\n", + "Storage is optimized via **Apache Iceberg** and a **Semi-Long Format**.\n", + "\n", + "### Why Iceberg?\n", + "- **Partition Pruning**: Skip entire matches during scan.\n", + "- **Bucket Pruning**: Read only specific players from a match file.\n", + "- **Schema Evolution**: Add new metrics/columns without rewriting the entire warehouse.\n", + "- **99% Pruning Efficiency**: Read 1MB of data for a specific player query vs. 100MB+ for the full match." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## 8. Get Involved & Discussion\n", + "\n", + "TDC is **open-source** and built on community collaboration.\n", + "\n", + "### Open Questions for Discussion:\n", + "- Should we support nested structs for joint data, or stick to flat hierarchical names?\n", + "- How do we best integrate event data (which doesn't fit the Arrow table model perfectly)?\n", + "- What are the priority formats for the next batch of loaders?\n", + "\n", + "**GitHub**: `PySport/tracking-data-container` \n", + "**RFC**: Join the conversation on the RFC document!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + }, + "livereveal": { + "scroll": true, + "theme": "simple", + "transition": "slide" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/20260423-presentation-liss/presentation.slides.html b/examples/20260423-presentation-liss/presentation.slides.html new file mode 100644 index 0000000..35a3cc1 --- /dev/null +++ b/examples/20260423-presentation-liss/presentation.slides.html @@ -0,0 +1,7740 @@ + + + + + + + +presentation slides + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/inspect_catalog.py b/inspect_catalog.py new file mode 100644 index 0000000..e26f529 --- /dev/null +++ b/inspect_catalog.py @@ -0,0 +1,26 @@ +import os +import tracking_data_container as tdc +import polars as pl + +def main(): + catalog_path = os.path.abspath("profile_warehouse") + catalog = tdc.TrackingDataCatalog(catalog_path) + + table = catalog._iceberg_catalog.load_table("tracking.tracking_data") + print("Table Schema:") + print(table.schema()) + + print("\nScanning table...") + arrow_table = table.scan().to_arrow() + df = pl.from_arrow(arrow_table) + print(f"Table row count: {len(df)}") + if len(df) > 0: + print("First 5 rows:") + print(df.head()) + print("\nUnique match/id:", df["match/id"].unique().to_list()) + print("Unique player/id:", df["player/id"].unique().to_list()[:10]) + else: + print("Table is EMPTY!") + +if __name__ == "__main__": + main() diff --git a/profile_load.py b/profile_load.py new file mode 100644 index 0000000..e3f4165 --- /dev/null +++ b/profile_load.py @@ -0,0 +1,114 @@ +import os +import time +import json +import shutil +from pathlib import Path +import polars as pl +import numpy as np +import pyarrow as pa +from tracking_data_container import TrackingDataContainer, TrackingDataCatalog +from tracking_data_container.schema import TrackedEntity, DataField + +def generate_synthetic_data(n_frames=146000, n_entities=25): + print(f"Generating synthetic data: {n_frames} frames, {n_entities} entities...") + + # Core columns + frames = np.arange(n_frames) + timestamps = frames * 40000 # 25 fps + + # To make it easier and faster to generate, we'll create the long format directly + print("Creating long-format dataframe...") + + # Repeat core columns for each entity + long_data = { + "match/id": np.tile(["m1"], n_frames * n_entities), + "frame_id": np.tile(frames, n_entities), + "timestamp": np.tile(timestamps, n_entities), + "period": np.tile(["1"], n_frames * n_entities), + } + + # Entities + # 1 ball, 11 home, 11 away, 2 referees = 25 + entity_keys = [] + # Ball + entity_keys.append("ball|none") + # Home team + for i in range(1, 12): + entity_keys.append(f"h{i}|home") + # Away team + for i in range(1, 12): + entity_keys.append(f"a{i}|away") + # Referees + for i in range(1, 3): + entity_keys.append(f"r{i}|referee") + + entity_keys = entity_keys[:n_entities] + + # Entity columns + p_ids = [] + t_ids = [] + e_keys = [] + for ek in entity_keys: + p_id, t_id = ek.split("|") + p_ids.extend([p_id] * n_frames) + t_ids.extend([t_id] * n_frames) + e_keys.extend([ek] * n_frames) + + long_data["player/id"] = p_ids + long_data["team/id"] = t_ids + long_data["entity_key"] = e_keys + + # Attributes: x, y, z, name + long_data["x"] = np.random.randn(n_frames * n_entities).astype(np.float32) + long_data["y"] = np.random.randn(n_frames * n_entities).astype(np.float32) + long_data["z"] = np.random.randn(n_frames * n_entities).astype(np.float32) + long_data["name"] = np.tile(["player_name"], n_frames * n_entities) + + df_long = pl.DataFrame(long_data) + + # We need to return a TrackingDataContainer. + # TDC is usually wide, so we pivot it first. + print("Pivoting to wide format for TrackingDataContainer...") + t0 = time.perf_counter() + container = TrackingDataContainer.pivot(df_long) + t1 = time.perf_counter() + print(f"Initial pivot took {t1 - t0:.2f}s") + + return container + +def profile_load(): + warehouse_path = Path("./profile_warehouse") + if warehouse_path.exists(): + shutil.rmtree(warehouse_path) + + catalog = TrackingDataCatalog(str(warehouse_path)) + catalog.initialize() + + container = generate_synthetic_data(n_frames=146000, n_entities=25) + + print("Saving to catalog...") + t0 = time.perf_counter() + catalog.save_container(container, match_id="m1") + t1 = time.perf_counter() + print(f"Save took {t1 - t0:.2f}s") + + print("\n--- Profiling catalog.load_container(match_id='m1').collect() ---") + + # 1. Total collect() call + t0 = time.perf_counter() + lazy_container = catalog.load_container(match_id="m1") + wide_container = lazy_container.collect() + t_collect = time.perf_counter() - t0 + + print(f"Total collect() time: {t_collect:.4f}s") + print(f"Wide container shape: {wide_container.num_rows()} rows, {len(wide_container.schema())} columns") + + # 2. Lazy.schema() reconstruction + t0 = time.perf_counter() + lazy_container = catalog.load_container(match_id="m1") # new one to avoid cache + _ = lazy_container.schema() + t_schema = time.perf_counter() - t0 + print(f"Lazy.schema() (metadata-based): {t_schema:.4f}s") + +if __name__ == "__main__": + profile_load() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07d69e1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,125 @@ +[build-system] +requires = ["maturin>=1.10,<2.0"] +build-backend = "maturin" + +[project] +name = "tracking_data_container" +description = "A shared, standardized, metadata-aware container for sports tracking data." +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Rust", +] +dynamic = ["version"] + +[project.optional-dependencies] +all = [ + "pandas>=1.4.4", + "polars>=0.16.14", + "pyarrow>=8.0.0", + "pyiceberg>=0.8.1", + "sqlalchemy>=2.0.0", +] +catalog = ["pyiceberg>=0.8.1", "sqlalchemy>=2.0.0"] +pandas = ["pandas>=1.4.4", "pyarrow>=8.0.0"] +polars = ["polars>=0.16.14"] +pyarrow = ["pyarrow>=8.0.0"] + +[dependency-groups] +dev = [ + "maturin>=1.7.0,<2.0", + "pandas>=1.4.4", + "polars>=0.16.14", + "pre-commit>=4.3.0", + "pyarrow>=8.0.0", + "pyiceberg>=0.8.1", + "sqlalchemy>=2.0.0", +] +all = [ + { include-group = "benchmarks" }, + { include-group = "docs" }, + { include-group = "linting" }, + { include-group = "nb" }, + { include-group = "testing" }, +] +benchmarks = [ + "fast-forward-football>=0.1.1; python_version == '3.11'", + "kloppy>=3.15.0", + { include-group = "testing" }, +] +docs = ["pdoc", { include-group = "dev" }] +linting = [ + "mypy>=1.11.2,<2", + "pandas-stubs", + "ruff>=0.12,<0.15", + { include-group = "dev" }, +] +nb = [ + "huggingface-hub>=1.6.0", + "ipykernel", + "matplotlib>=3.5.3", + "mplsoccer>=0.1.1", +] +testing = [ + "pytest>=7.1.3", + "pytest-benchmark>=4.0.0,<6", + { include-group = "dev" }, +] + +[tool.maturin] +features = ["pyo3/extension-module"] +manifest-path = "crates/tdc-python/Cargo.toml" +module-name = "tracking_data_container._tracking_data_container" +python-source = "python" + +[tool.pytest.ini_options] +testpaths = "python/tests" +log_cli = true +log_cli_level = "INFO" + +[tool.ruff] +line-length = 100 +target-version = "py39" + +[tool.ruff.lint] +select = ["E", "F", "I", "Q", "FA102", "UP"] + +[tool.uv] +package = false +required-version = ">=0.8.4" + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +check_untyped_defs = true +exclude = ["python/tests"] + +[[tool.mypy.overrides]] +module = [ + "pyarrow.*", + "mplsoccer.*", + "kloppy.*", + "fastforward.*", + "fast_forward_football.*", + "matplotlib.*", + "scipy.*", + "pytest.*", + "_pytest.*", +] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "python.tests.*" +ignore_errors = true diff --git a/python/tests/benchmarks/conftest.py b/python/tests/benchmarks/conftest.py new file mode 100644 index 0000000..9568f40 --- /dev/null +++ b/python/tests/benchmarks/conftest.py @@ -0,0 +1,12 @@ +def pytest_benchmark_generate_json(config, benchmarks, include_stats=None): + """Add peak memory information to the benchmark and print a summary.""" + print("\n\n" + "=" * 25 + " Memory Usage Summary " + "=" * 25) + print(f"{'Benchmark':<60} {'Peak Memory (MB)':>15}") + print("-" * 76) + + for benchmark in benchmarks: + mem = benchmark.extra_info.get("peak_memory_mb", 0) + # Inject into stats metadata + benchmark.stats.metadata["peak_memory_mb"] = mem + print(f"{benchmark.name:<60} {mem:>15.1f}") + print("=" * 72 + "\n") diff --git a/python/tests/benchmarks/test_add_column_benchmark.py b/python/tests/benchmarks/test_add_column_benchmark.py new file mode 100644 index 0000000..fbfca9c --- /dev/null +++ b/python/tests/benchmarks/test_add_column_benchmark.py @@ -0,0 +1,156 @@ +import polars as pl +import pytest +import tracking_data_container as tdc + + +@pytest.fixture +def large_container(): + """ + Creates a large container (100k frames, 22 players) for realistic benchmarking. + """ + num_frames = 100000 + num_players = 22 + data = { + "frame_id": list(range(num_frames)), + "ball/x": [1.0] * num_frames, + "ball/y": [2.0] * num_frames, + } + for p in range(num_players): + data[f"teams/home/players/p{p}/x"] = [float(p)] * num_frames + data[f"teams/home/players/p{p}/y"] = [float(p)] * num_frames + + return tdc.TrackingDataContainer(data) + + +# --- Speed/Velocity Benchmarks --- + + +def test_speed_tdc_expression(benchmark, large_container): + """ + Benchmark: Adding velocity using the internal TDC expression engine (DataFusion). + This performs lazy evaluation and parallelization but has some overhead for small batches. + """ + + def run(): + def expr_fn(x, y): + return (tdc.col(x).diff().pow(2.0) + tdc.col(y).diff().pow(2.0)).sqrt() + + return large_container.add_column_per_player("vel", expr_fn, on=["x", "y"]) + + benchmark(run) + + +def test_speed_polars_vectorized(benchmark, large_container): + """ + Benchmark: Adding velocity using a Python function working on Polars Series. + Uses zero-copy Arrow exchange to pass data to Polars for vectorized calculation. + """ + + def compute_speed(x, y): + x = pl.Series(x) + y = pl.Series(y) + return (x.diff().fill_null(0) ** 2 + y.diff().fill_null(0) ** 2).sqrt() + + def run(): + return large_container.add_column_per_player("vel", compute_speed, on=["x", "y"]) + + benchmark(run) + + +def test_speed_rust_kernel(benchmark, large_container): + """ + Benchmark: Adding velocity using a hand-optimized Rust kernel. + Avoids all Python overhead and uses parallelized vectorized Arrow math. + """ + + def run(): + return large_container.add_column_per_player("vel", tdc.ops.compute_speed, on=["x", "y"]) + + benchmark(run) + + +def test_speed_constant_baseline(benchmark, large_container): + """ + Benchmark: Adding a constant pre-computed list. + Represents the baseline overhead for metadata updates and column additions. + """ + num_rows = large_container.num_rows() + values = [1.0] * num_rows + + def run(): + return large_container.add_column_per_player("constant", values) + + benchmark(run) + + +# --- Distance to Ball Benchmarks --- + + +def test_dist_tdc_expression(benchmark, large_container): + """ + Benchmark: Calculating distance to ball using TDC expressions. + Tests the engine's ability to handle interactions between player and global columns. + """ + + def run(): + def expr_fn(x, y): + return ( + (tdc.col(x) - tdc.col("ball/x")).pow(2.0) + + (tdc.col(y) - tdc.col("ball/y")).pow(2.0) + ).sqrt() + + return large_container.add_column_per_player("dist", expr_fn, on=["x", "y"]) + + benchmark(run) + + +def test_dist_polars_vectorized(benchmark, large_container): + """ + Benchmark: Calculating distance to ball using a vectorized Polars function. + Leverages zero-copy transfer to perform the spatial calculation in a single pass. + """ + + def compute_dist(x, y, bx, by): + x, y, bx, by = pl.Series(x), pl.Series(y), pl.Series(bx), pl.Series(by) + return ((x - bx) ** 2 + (y - by) ** 2).sqrt() + + def run(): + names = [] + values = [] + ons = [] + for p in large_container.schema().players(): + tid, pid = p.team_id, p.player_id + prefix = f"teams/{tid}/players/{pid}/" + names.append(f"{prefix}dist") + values.append(compute_dist) + ons.append([f"{prefix}x", f"{prefix}y", "ball/x", "ball/y"]) + + return large_container.add_columns(names, values, on=ons) + + benchmark(run) + + +def test_dist_polars_copy_loop(benchmark, large_container): + """ + Benchmark: Calculating distance to ball by manually looping and copying Polars DataFrames. + Demonstrates the performance penalty of iterative container construction (O(N^2)). + """ + + def run(): + df = pl.DataFrame(large_container) + for p in large_container.schema().players(): + tid, pid = p.team_id, p.player_id + prefix = f"teams/{tid}/players/{pid}/" + df = df.with_columns( + [ + ( + (pl.col(f"{prefix}x") - pl.col("ball/x")) ** 2 + + (pl.col(f"{prefix}y") - pl.col("ball/y")) ** 2 + ) + .sqrt() + .alias(f"{prefix}dist") + ] + ) + return tdc.TrackingDataContainer(df) + + benchmark(run) diff --git a/python/tests/benchmarks/test_catalog_performance.py b/python/tests/benchmarks/test_catalog_performance.py new file mode 100644 index 0000000..65f7c40 --- /dev/null +++ b/python/tests/benchmarks/test_catalog_performance.py @@ -0,0 +1,68 @@ +import numpy as np +import pytest +import tracking_data_container as tdc + + +@pytest.fixture +def performance_catalog(tmp_path): + warehouse_path = tmp_path / "perf_warehouse" + catalog = tdc.TrackingDataCatalog(str(warehouse_path)) + catalog.initialize() + + # Generate 100k frames for 25 entities + num_frames = 100_000 + entities = ["ball"] + [f"p{i}" for i in range(22)] + ["ref1", "ref2"] + + data = { + "match/id": ["match_1"] * num_frames, + "timestamp": [i * 40000 for i in range(num_frames)], + "frame_id": list(range(num_frames)), + "period": ["1"] * num_frames, + } + + for ent in entities: + if ent == "ball": + prefix = "ball/" + else: + prefix = f"teams/home/players/{ent}/" if ent.startswith("p") else f"referees/{ent}/" + + data[f"{prefix}x"] = np.random.randn(num_frames).tolist() + data[f"{prefix}y"] = np.random.randn(num_frames).tolist() + + container = tdc.TrackingDataContainer(data, validate=False) + catalog.save_container(container) + return catalog + + +def test_benchmark_catalog_load_lazy(performance_catalog, benchmark): + """Benchmark the initialization of a lazy container from catalog.""" + + def load(): + return performance_catalog.load_container(match_id="match_1") + + lazy_c = benchmark(load) + assert isinstance(lazy_c, tdc.LazyTrackingDataContainer) + + +def test_benchmark_catalog_collect_full(performance_catalog, benchmark): + """Benchmark the full collection (pivot) of a standard match.""" + lazy_c = performance_catalog.load_container(match_id="match_1") + + def collect_full(): + return lazy_c.collect() + + container = benchmark(collect_full) + assert container.num_rows() == 100_000 + + +def test_benchmark_catalog_collect_selection(performance_catalog, benchmark): + """Benchmark targeted selection collect (Selection Pushdown).""" + lazy_c = performance_catalog.load_container(match_id="match_1") + + def collect_selection(): + # Only pivot the ball - should be very fast + return lazy_c.select(["ball/x", "ball/y"]).collect() + + container = benchmark(collect_selection) + assert container.num_rows() == 100_000 + assert len(container.schema().names()) == 2 diff --git a/python/tests/benchmarks/test_hawkeye.py b/python/tests/benchmarks/test_hawkeye.py new file mode 100644 index 0000000..3b8dd3c --- /dev/null +++ b/python/tests/benchmarks/test_hawkeye.py @@ -0,0 +1,108 @@ +import re +import tracemalloc +from pathlib import Path +from typing import Union + +import polars as pl +import pytest +import tracking_data_container as tdc + +try: + from kloppy import hawkeye + + KLOPPY_AVAILABLE = True +except ImportError: + KLOPPY_AVAILABLE = False + +try: + import fastforward as fff + + _ = fff.hawkeye + FFF_AVAILABLE = True +except (ImportError, AttributeError): + FFF_AVAILABLE = False + + +def _natural_sort_key(path: Union[str, Path]) -> list[Union[int, str]]: + # Split string into list of chunks for natural sorting + return [int(text) if text.isdigit() else text.lower() for text in re.split(r"(\d+)", str(path))] + + +HAWKEYE_DIR = Path(__file__).parents[3] / "data" / "hawkeye" +# Limit to 5 minutes for benchmarks to keep it reasonable +BALL_FILES = sorted(list(HAWKEYE_DIR.glob("*.samples.ball.json")), key=_natural_sort_key)[:5] +CENTROID_FILES = sorted(list(HAWKEYE_DIR.glob("*.samples.centroids.json")), key=_natural_sort_key)[ + :5 +] +JOINT_FILES = sorted(list(HAWKEYE_DIR.glob("*.samples.joints.json")), key=_natural_sort_key)[:5] +HAWKEYE_META = HAWKEYE_DIR / "meta.json" + + +@pytest.mark.benchmark(group="hawkeye") +def test_hawkeye_tdc_parsers(benchmark): + def run(): + return tdc.load_hawkeye( + BALL_FILES, CENTROID_FILES, joint_paths=JOINT_FILES, meta_path=HAWKEYE_META + ).to_polars() + + tracemalloc.start() + # Using pedantic to be consistent with others + df = benchmark.pedantic(run, rounds=5, iterations=1) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.skipif(not KLOPPY_AVAILABLE, reason="kloppy not installed") +@pytest.mark.benchmark(group="hawkeye") +def test_hawkeye_kloppy(benchmark): + def run(): + # Kloppy's hawkeye loader expects ball_feeds and player_centroid_feeds + # Note: Kloppy might not support joint feeds yet or uses different args. + # Based on previous research, it only supported ball and centroids. + dataset = hawkeye.load( + ball_feeds=[str(p) for p in BALL_FILES], + player_centroid_feeds=[str(p) for p in CENTROID_FILES], + meta_data=str(HAWKEYE_META), + ) + return dataset + + tracemalloc.start() + # Kloppy is slow, we use pedantic to limit rounds + dataset = benchmark.pedantic(run, rounds=1, iterations=1) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert dataset is not None + + +@pytest.mark.skipif(not FFF_AVAILABLE, reason="fastforward.hawkeye not available") +@pytest.mark.benchmark(group="hawkeye") +def test_hawkeye_fff(benchmark): + def run(): + # Fastforward supports joint_data + dataset = fff.hawkeye.load_tracking( + ball_data=[str(p) for p in BALL_FILES], + player_data=[str(p) for p in CENTROID_FILES], + meta_data=str(HAWKEYE_META), + layout="wide", + ) + # Use .tracking which is typically the Polars DataFrame in fff + return dataset.tracking + + tracemalloc.start() + # Fastforward can be slow, we use pedantic to limit rounds + df = benchmark.pedantic(run, rounds=1, iterations=1) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 diff --git a/python/tests/benchmarks/test_lazy_filtering.py b/python/tests/benchmarks/test_lazy_filtering.py new file mode 100644 index 0000000..2081c96 --- /dev/null +++ b/python/tests/benchmarks/test_lazy_filtering.py @@ -0,0 +1,85 @@ +import tracemalloc +from pathlib import Path + +import polars as pl +import pytest +import tracking_data_container as tdc + +STATSPERFORM_FILE = Path(__file__).parents[3] / "data" / "statsperform" / "ma25.txt" + + +@pytest.fixture(scope="session") +def statsperform_parquet(tmp_path_factory): + tmp_dir = tmp_path_factory.mktemp("data") + parquet_path = tmp_dir / "statsperform.parquet" + + # Load and save to parquet + container = tdc.load_statsperform(str(STATSPERFORM_FILE)) + container.to_parquet(parquet_path) + + return parquet_path + + +@pytest.mark.benchmark(group="lazy_filtering") +def test_lazy_filtering_tdc(benchmark, statsperform_parquet): + def run(): + # Lazy scan, filter for first half, and collect + return ( + tdc.scan_parquet(str(statsperform_parquet)) + .filter(tdc.col("period") == tdc.lit("first_half")) + .collect() + ) + + tracemalloc.start() + res = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(res, tdc.TrackingDataContainer) + assert res.num_rows() > 0 + + +@pytest.mark.benchmark(group="lazy_filtering") +def test_lazy_filtering_tdc_selective(benchmark, statsperform_parquet): + def run(): + # Lazy scan, filter, SELECT, and collect + return ( + tdc.scan_parquet(str(statsperform_parquet)) + .filter(tdc.col("period") == tdc.lit("first_half")) + .select(["frame_id", "period", "ball/x", "ball/y"]) + .collect() + ) + + tracemalloc.start() + res = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(res, tdc.TrackingDataContainer) + assert res.num_rows() > 0 + + +@pytest.mark.benchmark(group="lazy_filtering") +def test_lazy_filtering_polars(benchmark, statsperform_parquet): + def run(): + # Polars scan, filter, and collect + return ( + pl.scan_parquet(statsperform_parquet).filter(pl.col("period") == "first_half").collect() + ) + + tracemalloc.start() + res = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(res, pl.DataFrame) + assert len(res) > 0 diff --git a/python/tests/benchmarks/test_sportec.py b/python/tests/benchmarks/test_sportec.py new file mode 100644 index 0000000..f489309 --- /dev/null +++ b/python/tests/benchmarks/test_sportec.py @@ -0,0 +1,123 @@ +import tracemalloc +from pathlib import Path + +import polars as pl +import pytest +import tracking_data_container as tdc + +try: + from kloppy import sportec + + KLOPPY_AVAILABLE = True +except ImportError: + KLOPPY_AVAILABLE = False + +try: + import fastforward as fff + + # Try to access submodules to verify availability + _ = fff.sportec + FFF_AVAILABLE = True +except (ImportError, AttributeError): + FFF_AVAILABLE = False + +SPORTEC_FILE = Path(__file__).parents[3] / "data" / "sportec" / "tracking.xml" +SPORTEC_META = Path(__file__).parents[3] / "data" / "sportec" / "meta.xml" + + +def tdc_parse_sportec(path: Path) -> pl.DataFrame: + import xml.etree.ElementTree as ET + + builder = tdc.TrackingDataBuilder() + tree = ET.parse(path) + root = tree.getroot() + + for frame_set in root.findall(".//FrameSet"): + team_id = frame_set.get("TeamId") + person_id = frame_set.get("PersonId") + + if not team_id or not person_id: + continue + + col_x = f"teams/{team_id}/players/{person_id}/x" + col_y = f"teams/{team_id}/players/{person_id}/y" + + for frame in frame_set.findall("Frame"): + frame_n = int(frame.get("N")) + x = float(frame.get("X")) + y = float(frame.get("Y")) + + builder.insert(frame_n, col_x, x) + builder.insert(frame_n, col_y, y) + + return builder.build().to_polars() + + +@pytest.mark.benchmark(group="sportec") +def test_sportec_tdc_parsers(benchmark): + def run(): + return tdc.load_sportec(str(SPORTEC_FILE)).to_polars() + + tracemalloc.start() + df = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.benchmark(group="sportec") +def test_sportec_tdc_python(benchmark): + tracemalloc.start() + df = benchmark.pedantic( + tdc_parse_sportec, kwargs={"path": SPORTEC_FILE}, rounds=1, iterations=1 + ) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.skipif(not KLOPPY_AVAILABLE, reason="kloppy not installed") +@pytest.mark.benchmark(group="sportec") +def test_sportec_kloppy(benchmark): + def run(): + dataset = sportec.load_tracking( + raw_data=str(SPORTEC_FILE), meta_data=str(SPORTEC_META), coordinates="sportec" + ) + return dataset.to_df(engine="polars") + + tracemalloc.start() + # Kloppy is slow, we use pedantic to limit rounds + df = benchmark.pedantic(run, rounds=1, iterations=1) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.skipif(not FFF_AVAILABLE, reason="fastforward.sportec not available") +@pytest.mark.benchmark(group="sportec") +def test_sportec_fff(benchmark): + def run(): + dataset = fff.sportec.load_tracking(str(SPORTEC_FILE), str(SPORTEC_META)) + return dataset.tracking + + tracemalloc.start() + df = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + benchmark.extra_info["peak_memory_mb"] = peak / 1024 / 1024 + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 diff --git a/python/tests/benchmarks/test_statsperform.py b/python/tests/benchmarks/test_statsperform.py new file mode 100644 index 0000000..7f38e41 --- /dev/null +++ b/python/tests/benchmarks/test_statsperform.py @@ -0,0 +1,153 @@ +import tracemalloc +from pathlib import Path + +import polars as pl +import pytest +import tracking_data_container as tdc + +try: + from kloppy import statsperform + + KLOPPY_AVAILABLE = True +except ImportError: + KLOPPY_AVAILABLE = False + +try: + import fastforward as fff + + _ = fff.statsperform + FFF_AVAILABLE = True +except (ImportError, AttributeError): + FFF_AVAILABLE = False + +STATSPERFORM_FILE = Path(__file__).parents[3] / "data" / "statsperform" / "ma25.txt" +STATSPERFORM_META = Path(__file__).parents[3] / "data" / "statsperform" / "ma1.json" + + +def tdc_parse_statsperform(path: Path) -> pl.DataFrame: + builder = tdc.TrackingDataBuilder() + + with open(path) as f: + for i, line in enumerate(f): + line = line.strip() + if not line: + continue + if i == 0 and ";" not in line: + continue + + parts = line.split(":") + frame_part = parts[0] + players_part = parts[1] + ball_part = parts[2] if len(parts) > 2 else None + + frame_id_str, info_str = frame_part.split(";") + frame_id = int(frame_id_str) + + if players_part != ";" and players_part: + for p_data in players_part.split(";"): + if not p_data: + continue + p_parts = p_data.split(",") + if not p_parts[0]: + continue + + team_side = int(p_parts[0]) + if team_side == 2: + continue # Referee + + team_id = "home" if team_side in (0, 3) else "away" + player_id = p_parts[1] + x = float(p_parts[3]) + y = float(p_parts[4]) + + builder.insert(frame_id, f"teams/{team_id}/players/{player_id}/x", x) + builder.insert(frame_id, f"teams/{team_id}/players/{player_id}/y", y) + + if ball_part: + b_info = ball_part.split(";")[0] + b_parts = b_info.split(",") + if len(b_parts) >= 2: + builder.insert(frame_id, "ball/x", float(b_parts[0])) + builder.insert(frame_id, "ball/y", float(b_parts[1])) + + return builder.build().to_polars() + + +@pytest.mark.benchmark(group="statsperform") +def test_statsperform_tdc_parsers(benchmark): + def run(): + return tdc.load_statsperform(str(STATSPERFORM_FILE)).to_polars() + + tracemalloc.start() + df = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.benchmark(group="statsperform") +def test_statsperform_tdc_python(benchmark): + tracemalloc.start() + df = benchmark.pedantic( + tdc_parse_statsperform, kwargs={"path": STATSPERFORM_FILE}, rounds=1, iterations=1 + ) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.skipif(not KLOPPY_AVAILABLE, reason="kloppy not installed") +@pytest.mark.benchmark(group="statsperform") +def test_statsperform_kloppy(benchmark): + def run(): + dataset = statsperform.load_tracking( + ma1_data=str(STATSPERFORM_META), + ma25_data=str(STATSPERFORM_FILE), + coordinates="sportvu", + ) + return dataset.to_df(engine="polars") + + tracemalloc.start() + # Kloppy is slow, we use pedantic to limit rounds + df = benchmark.pedantic(run, rounds=1, iterations=1) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 + + +@pytest.mark.skipif(not FFF_AVAILABLE, reason="fastforward.statsperform not available") +@pytest.mark.benchmark(group="statsperform") +def test_statsperform_fff(benchmark): + def run(): + dataset = fff.statsperform.load_tracking( + ma1_data=str(STATSPERFORM_META), + ma25_data=str(STATSPERFORM_FILE), + ) + # Use .tracking which is the Polars DataFrame + return dataset.tracking + + tracemalloc.start() + df = benchmark(run) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + + peak_mb = peak / 1024 / 1024 + benchmark.extra_info["peak_memory_mb"] = peak_mb + + assert isinstance(df, pl.DataFrame) + assert len(df) > 0 diff --git a/python/tests/conftest.py b/python/tests/conftest.py new file mode 100644 index 0000000..7b8870b --- /dev/null +++ b/python/tests/conftest.py @@ -0,0 +1,59 @@ +from datetime import datetime, timedelta, timezone + +import pytest +import tracking_data_container as tdc + + +@pytest.fixture +def sample_data(): + base_time = datetime(2023, 10, 1, 12, 0, 0, tzinfo=timezone.utc) + return { + "frame_id": [123456, 123457, 123458], + "timestamp": [ + base_time, + base_time + timedelta(milliseconds=40), + base_time + timedelta(milliseconds=80), + ], + "period": ["first_half", "first_half", "first_half"], + "match/id": ["match_12345", "match_12345", "match_12345"], + "ball_status": [True, True, True], + "teams/home/id": ["team_1", "team_1", "team_1"], + "teams/home/players/player_1/x": [23.5, 23.6, 23.7], + "teams/home/players/player_1/y": [24.2, 24.3, 24.4], + "teams/home/players/player_2/x": [56.3, 56.4, 56.5], + "teams/home/players/player_2/y": [12.8, 12.9, 13.0], + "teams/away/id": ["team_2", "team_2", "team_2"], + "teams/away/players/player_3/x": [33.1, 33.2, 33.3], + "teams/away/players/player_3/y": [34.9, 35.0, 35.1], + "teams/away/players/player_4/x": [45.6, 45.7, 45.8], + "teams/away/players/player_4/y": [24.0, 24.1, 24.2], + "ball/x": [50.0, 51.0, 52.0], + "ball/y": [12.0, 13.0, 14.0], + "ball/z": [0.5, 0.6, 0.7], + } + + +@pytest.fixture +def container(sample_data): + return tdc.TrackingDataContainer(sample_data) + + +@pytest.fixture +def parquet_file(tmp_path, container): + path = tmp_path / "test_data.parquet" + container.to_parquet(path) + return path + + +@pytest.fixture +def empty_catalog(tmp_path): + warehouse_path = tmp_path / "warehouse" + catalog = tdc.TrackingDataCatalog(str(warehouse_path)) + catalog.initialize() + return catalog + + +@pytest.fixture +def iceberg_catalog(empty_catalog, container): + empty_catalog.save_container(container) + return empty_catalog diff --git a/python/tests/test_build.py b/python/tests/test_build.py new file mode 100644 index 0000000..527dcbb --- /dev/null +++ b/python/tests/test_build.py @@ -0,0 +1,93 @@ +from datetime import datetime, timedelta, timezone + +import tracking_data_container as tdc + + +class TestBuilder: + def test_tracking_data_builder(self) -> None: + builder = tdc.TrackingDataBuilder() + + base_time = datetime(2023, 10, 1, 12, 0, 0, tzinfo=timezone.utc) + ts1 = int(base_time.timestamp() * 1_000_000) + ts2 = int((base_time + timedelta(milliseconds=40)).timestamp() * 1_000_000) + + # Insert data for frame 1 + builder.insert(1, "frame_id", 1) + builder.insert(1, "timestamp", ts1) + builder.insert(1, "ball/x", 10) + builder.insert(1, "ball/y", 20.5) + builder.insert(1, "period", "first_half") + + # Insert data for frame 2 + builder.insert(2, "frame_id", 2) + builder.insert(2, "timestamp", ts2) + builder.insert(2, "ball/x", 15) + builder.insert(2, "ball/y", 25.5) + builder.insert(2, "period", "first_half") + + container = builder.build() + assert container.num_rows() == 2 + + # 1. Materialize by index + frame_0 = container.materialize_frame(0) + assert frame_0[tdc.DataField("ball/x")] == 10 + assert frame_0["ball/x"] == 10 + assert frame_0[tdc.DataField("ball/y")] == 20.5 + assert frame_0["ball/y"] == 20.5 + assert frame_0[tdc.DataField("period")] == "first_half" + assert frame_0["period"] == "first_half" + + # Check to_dict and repr + assert repr(frame_0) == "" + d = frame_0.to_dict() + assert d["frame_id"] == 1 + assert d["ball/x"] == 10 + assert d["ball/y"] == 20.5 + assert d["period"] == "first_half" + + # 2. Materialize by datetime + frame_ts = container.materialize_frame(base_time + timedelta(milliseconds=40)) + assert frame_ts[tdc.DataField("ball/x")] == 15 + + # 3. Materialize by timedelta + frame_td = container.materialize_frame(timedelta(milliseconds=0)) + assert frame_td[tdc.DataField("ball/x")] == 10 + + def test_tracking_data_builder_merge(self) -> None: + base_time = datetime(2023, 10, 1, 12, 0, 0, tzinfo=timezone.utc) + ts1 = int(base_time.timestamp() * 1_000_000) + ts2 = int((base_time + timedelta(milliseconds=40)).timestamp() * 1_000_000) + + # Builder 1 with some data + b1 = tdc.TrackingDataBuilder() + b1.insert(1, "frame_id", 1) + b1.insert(1, "timestamp", ts1) + b1.insert(1, "ball/x", 10) + + # Builder 2 with overlapping and new data + b2 = tdc.TrackingDataBuilder() + b2.insert(1, "frame_id", 1) + b2.insert(1, "timestamp", ts1) + b2.insert(1, "ball/y", 20) # same frame as b1 + b2.insert(2, "frame_id", 2) + b2.insert(2, "timestamp", ts2) + b2.insert(2, "ball/x", 30) # new frame + + # Test DataField as column + field = tdc.DataField("ball/z") + b2.insert(2, field, 40.5) + + b1.merge(b2) + + container = b1.build() + assert container.num_rows() == 2 + + # Frame 1 should have x and y + f1 = container.materialize_frame(timedelta(seconds=0)) + assert f1["ball/x"] == 10 # type: ignore + assert f1["ball/y"] == 20 # type: ignore + + # Frame 2 should have x and z + f2 = container.materialize_frame(1) + assert f2["ball/x"] == 30 # type: ignore + assert f2["ball/z"] == 40.5 # type: ignore diff --git a/python/tests/test_catalog.py b/python/tests/test_catalog.py new file mode 100644 index 0000000..ac597c6 --- /dev/null +++ b/python/tests/test_catalog.py @@ -0,0 +1,191 @@ +import polars as pl +import pytest +import tracking_data_container as tdc + + +class TestIcebergCatalog: + def test_catalog_lifecycle(self, empty_catalog): + # Create a container with all required fields for Iceberg + # and some constant attributes (jersey_color, player_name) + # Including a NULL value in x to verify it's allowed + data = { + "match/id": ["match_1", "match_1"], + "timestamp": [1000000, 1040000], # microseconds + "frame_id": [100, 101], + "period": ["1", "1"], + "ball/x": [0.0, None], + "ball/y": [0.0, 1.0], + "ball/radius": [0.11, 0.11], # Ball constant + "teams/home/players/p1/x": [10.0, 11.0], + "teams/home/players/p1/y": [20.0, 21.0], + "teams/home/players/p1/jersey_color": ["red", "red"], + "teams/home/players/p1/name": ["Player 1", "Player 1"], + } + container = tdc.TrackingDataContainer(data, validate=False) + + # Save container - normalization should happen here + empty_catalog.save_container(container) + + # Verify normalization: physical table should not have jersey_color + table = empty_catalog._iceberg_catalog.load_table("tracking.tracking_data") + physical_cols = [f.name for f in table.schema().fields] + assert "jersey_color" not in physical_cols + assert "name" not in physical_cols + assert "radius" not in physical_cols + + # Load container + lazy_loaded = empty_catalog.load_container(match_id="match_1") + assert isinstance(lazy_loaded, tdc.LazyTrackingDataContainer) + + # Schema should include normalized columns + schema_names = lazy_loaded.schema().names() + assert "teams/home/players/p1/jersey_color" in schema_names + assert "teams/home/players/p1/name" in schema_names + assert "ball/radius" in schema_names + + loaded = lazy_loaded.collect() + assert loaded.num_rows() == 2 + assert loaded.is_bound() + + # Check data + df = loaded.to_polars() + assert df["match/id"][0] == "match_1" + # ball should NOT have player/id + assert "ball/player/id" not in df.columns + assert df["ball/x"][0] == 0.0 + assert df["ball/x"][1] is None + + # Check normalized constants + assert df["teams/home/players/p1/jersey_color"][0] == "red" + assert df["teams/home/players/p1/name"][0] == "Player 1" + assert df["ball/radius"][0] == "0.11" + + def test_bound_container_save(self, iceberg_catalog): + # Load (becomes bound) + lazy_bound = iceberg_catalog.load_container(match_id="match_12345") + bound = lazy_bound.collect() + assert bound.is_bound() + + # Modify and save via bound method + # Preserves catalog binding + new_bound = bound.add_column("ball/z2", [1.0, 1.0, 1.0]) + assert new_bound.is_bound() + + new_bound.save() + + # Load again to verify + final = iceberg_catalog.load_container(match_id="match_12345").collect() + assert "ball/z2" in final.to_polars().columns + assert final.to_polars()["ball/z2"][0] == 1.0 + + def test_iceberg_backend_errors(self, iceberg_catalog): + lazy = iceberg_catalog.load_container(match_id="match_12345") + + # Filter is now supported via deferred evaluation + filtered = lazy.filter(tdc.col("ball/x") > 0) + assert isinstance(filtered, tdc.LazyTrackingDataContainer) + + with pytest.raises(NotImplementedError, match="Iceberg backend does not support"): + lazy.transform_coordinates(tdc.CoordinateSystem(origin="bottom_left")) + + def test_load_non_existent(self, empty_catalog): + # Loading a non-existent match id should return a lazy container that collects to 0 rows + lazy = empty_catalog.load_container(match_id="non_existent") + loaded = lazy.collect() + assert loaded.num_rows() == 0 + + def test_physical_parquet_format(self, iceberg_catalog): + import glob + import os + + # Find the physical parquet files written by Iceberg + warehouse_dir = iceberg_catalog.warehouse_path + parquet_files = glob.glob( + os.path.join(warehouse_dir, "tracking", "tracking_data", "**", "*.parquet"), + recursive=True, + ) + assert len(parquet_files) > 0 + + # Read the parquet files directly with Polars + df = pl.read_parquet(parquet_files[0]) + + # Verify it is in long format with the expected columns + expected_cols = { + "competition_id", + "season_id", + "match_id", + "frame_id", + "period", + "timestamp", + "ball_status", + "entity_type", + "entity_id", + "entity_key", + "x", + "y", + "z", + } + assert set(df.columns) == expected_cols + + +class TestMeltPivot: + def test_melt_pivot_roundtrip(self): + """Test that melting and pivoting correctly reconstructs the wide format.""" + data = { + "match/id": ["m1", "m1"], + "timestamp": [1000000, 1040000], + "frame_id": [1, 2], + "period": ["1", "1"], + "ball/x": [10.0, 11.0], + "ball/y": [20.0, 21.0], + "teams/home/players/p1/x": [30.0, 31.0], + } + container = tdc.TrackingDataContainer(data, validate=False) + + # Melt to long format + long_df = container.melt() + assert "player/id" in long_df.columns + assert "entity_key" in long_df.columns + + # Pivot back to wide + pivoted = tdc.TrackingDataContainer.pivot(long_df) + + assert pivoted.num_rows() == 2 + assert "ball/x" in pivoted.schema().names() + assert "teams/home/players/p1/x" in pivoted.schema().names() + + # Check values + df_piv = pivoted.to_polars().sort("frame_id") + assert df_piv["ball/x"].to_list() == [10.0, 11.0] + assert df_piv["teams/home/players/p1/x"].to_list() == [30.0, 31.0] + + def test_pivot_global_constants(self): + """Test that global constants are correctly handled in pivot.""" + data = { + "match_id": ["constant_match"] * 4, + "competition_id": ["constant_comp"] * 4, + "timestamp": [1000, 1000, 2000, 2000], + "period": ["1", "1", "1", "1"], + "entity_id": ["p1", "p2", "p1", "p2"], + "team_id": ["home", "home", "home", "home"], + "entity_key": ["p1|home", "p2|home", "p1|home", "p2|home"], + "x": [1.0, 2.0, 3.0, 4.0], + "y": [1.0, 2.0, 3.0, 4.0], + } + df = pl.DataFrame(data) + + pivoted = tdc.TrackingDataContainer.pivot(df) + + # Check that constants are back (mapped to internal CDF names by Rust) + res = pivoted.to_polars() + assert "match/id" in res.columns + assert "competition/id" in res.columns + assert res["match/id"][0] == "constant_match" + + assert "competition/id" in res.columns + assert res["competition/id"][0] == "constant_comp" + + # Check pivoted columns + assert "teams/home/players/p1/x" in res.columns + assert "teams/home/players/p2/x" in res.columns + assert res.sort("timestamp")["teams/home/players/p1/x"].to_list() == [1.0, 3.0] diff --git a/python/tests/test_container.py b/python/tests/test_container.py new file mode 100644 index 0000000..7dfc481 --- /dev/null +++ b/python/tests/test_container.py @@ -0,0 +1,126 @@ +import numpy as np +import pytest +import tracking_data_container as tdc + + +class TestBasics: + def test_container_init(self, container) -> None: + assert isinstance(container, tdc.TrackingDataContainer) + assert container.num_rows() == 3 + assert len(container.schema()) == 18 + + def test_data_field_init(self) -> None: + field = tdc.DataField("ball/x") + assert field.entity.is_ball + assert field.attribute == "x" + assert field.name() == "ball/x" + + def test_orientation_mode(self, container) -> None: + # Default container has no orientation set + assert container.orientation_mode is None + + # Set orientation + container = container.with_orientation(tdc.OrientationMode.HomeAway) + assert container.orientation_mode == tdc.OrientationMode.HomeAway + + +class TestAddColumn: + def test_add_column_array(self, container): + data = [1.0, 2.0, 3.0] + container = container.add_column("ball/new", data) + assert "ball/new" in container.schema().names() + assert container.to_arrow().column("ball/new").to_pylist() == data + + def test_add_column_expr(self, container): + expr = (tdc.col("ball/x") + tdc.col("ball/y")).sqrt() + container = container.add_column("ball/dist", expr) + assert "ball/dist" in container.schema().names() + + # ball/x: [50.0, 51.0, 52.0], ball/y: [12.0, 13.0, 14.0] + expected = np.sqrt(np.array([50.0, 51.0, 52.0]) + np.array([12.0, 13.0, 14.0])) + actual = container.to_arrow().column("ball/dist").to_numpy() + np.testing.assert_allclose(actual, expected) + + def test_add_column_udf(self, container): + def my_udf(x): + return x.to_numpy() + 10.0 + + container = container.add_column("ball/x_plus_10", my_udf, on=["ball/x"]) + assert "ball/x_plus_10" in container.schema().names() + + # ball/x: [50.0, 51.0, 52.0] + expected = np.array([60.0, 61.0, 62.0]) + actual = container.to_arrow().column("ball/x_plus_10").to_numpy() + np.testing.assert_allclose(actual, expected) + + def test_add_column_ops(self, container): + # Test the tdc.ops.compute_speed rust kernel + container = container.add_column( + "ball/speed", tdc.ops.compute_speed, on=["ball/x", "ball/y"] + ) + assert "ball/speed" in container.schema().names() + + # x: [50.0, 51.0, 52.0], y: [12.0, 13.0, 14.0] + # dx=1, dy=1 -> speed=sqrt(2) approx 1.414 + expected = np.array([0.0, np.sqrt(2), np.sqrt(2)]) + actual = container.to_arrow().column("ball/speed").to_numpy() + np.testing.assert_allclose(actual, expected, atol=1e-6) + + def test_add_column_per_player_udf(self, container): + def compute_pos_sum(x, y): + return x.to_numpy() + y.to_numpy() + + container = container.add_column_per_player("pos_sum", compute_pos_sum, on=["x", "y"]) + assert "teams/home/players/player_1/pos_sum" in container.schema().names() + + # home player_1 x: [23.5, 23.6, 23.7], y: [24.2, 24.3, 24.4] + expected = np.array([23.5 + 24.2, 23.6 + 24.3, 23.7 + 24.4]) + actual = container.to_arrow().column("teams/home/players/player_1/pos_sum").to_numpy() + np.testing.assert_allclose(actual, expected) + + def test_add_column_per_player_ops(self, container): + container = container.add_column_per_player( + "speed_rust", tdc.ops.compute_speed, on=["x", "y"] + ) + assert "teams/home/players/player_1/speed_rust" in container.schema().names() + + # home player_1 x: [23.5, 23.6, 23.7], y: [24.2, 24.3, 24.4] + # dx=0.1, dy=0.1 -> speed=sqrt(0.01 + 0.01) = sqrt(0.02) approx 0.1414 + expected = np.array([0.0, np.sqrt(0.02), np.sqrt(0.02)]) + actual = container.to_arrow().column("teams/home/players/player_1/speed_rust").to_numpy() + np.testing.assert_allclose(actual, expected, atol=1e-6) + + def test_container_add_column_direct(self, container) -> None: + new_values = [1.0, 2.0, 3.0] + container = container.add_column("test/new_col", new_values) + assert "test/new_col" in container.to_polars().columns + assert container.to_polars()["test/new_col"].to_list() == new_values + + def test_container_add_column_callable(self, container) -> None: + def compute_sum(frame): + return frame["ball/x"].to_numpy() + frame["ball/y"].to_numpy() + + container = container.add_column("ball/sum", compute_sum) + assert "ball/sum" in container.to_polars().columns + # Samples: [50.0, 51.0, 52.0] + [12.0, 13.0, 14.0] = [62.0, 64.0, 66.0] + res = container.to_polars()["ball/sum"].to_list() + assert res[0] == pytest.approx(62.0) + assert res[1] == pytest.approx(64.0) + assert res[2] == pytest.approx(66.0) + + def test_container_add_column_callable_on(self, container) -> None: + def compute_sum(x, y): + return x.to_numpy() + y.to_numpy() + + container = container.add_column("ball/sum_on", compute_sum, on=["ball/x", "ball/y"]) + assert "ball/sum_on" in container.to_polars().columns + res = container.to_polars()["ball/sum_on"].to_list() + assert res[0] == pytest.approx(62.0) + assert res[1] == pytest.approx(64.0) + assert res[2] == pytest.approx(66.0) + + def test_container_add_column_replace(self, container) -> None: + # Replacing an existing column + new_x = [100.0, 200.0, 300.0] + container = container.add_column("ball/x", new_x) + assert container.to_polars()["ball/x"].to_list() == pytest.approx(new_x) diff --git a/python/tests/test_errors.py b/python/tests/test_errors.py new file mode 100644 index 0000000..71ba5ce --- /dev/null +++ b/python/tests/test_errors.py @@ -0,0 +1,15 @@ +import pytest +import tracking_data_container as tdc + + +@pytest.mark.parametrize( + "exc_class, expected_docstring", + [ + (tdc.exceptions.TrackingDataContainerError, "The base class for all errors"), + (tdc.exceptions.InvalidParametersError, "Provided parameters are invalid"), + (tdc.exceptions.SchemaError, "Schema validation failed"), + (tdc.exceptions.SchemaWarning, "Schema validation warning"), + ], +) +def test_docstrings(exc_class: type[Exception], expected_docstring: str) -> None: + assert exc_class.__doc__ == expected_docstring diff --git a/python/tests/test_interop.py b/python/tests/test_interop.py new file mode 100644 index 0000000..442b284 --- /dev/null +++ b/python/tests/test_interop.py @@ -0,0 +1,61 @@ +import pandas as pd +import polars as pl +import pyarrow as pa +import tracking_data_container as tdc + + +def test_to_arrow(container): + batch = container.to_arrow() + assert isinstance(batch, pa.RecordBatch) + assert batch.num_rows == 3 + assert batch.num_columns == 18 + assert batch.column(batch.schema.get_field_index("ball/x")).to_pylist() == [50.0, 51.0, 52.0] + assert batch.column(batch.schema.get_field_index("ball/y")).to_pylist() == [12.0, 13.0, 14.0] + + +def test_to_pandas(container): + df = container.to_pandas() + assert isinstance(df, pd.DataFrame) + assert len(df) == 3 + assert "ball/x" in df.columns + assert "ball/y" in df.columns + assert df["ball/x"].tolist() == [50.0, 51.0, 52.0] + + +def test_to_polars(container): + df = container.to_polars() + assert isinstance(df, pl.DataFrame) + assert df.height == 3 + assert "ball/x" in df.columns + assert "ball/y" in df.columns + assert df["ball/x"].to_list() == [50.0, 51.0, 52.0] + + +def test_zero_copy_polars(container): + # Polars can consume the container directly via the PyCapsule interface + df = pl.DataFrame(container) + assert df.height == 3 + assert df["ball/x"].to_list() == [50.0, 51.0, 52.0] + + +def test_parquet_roundtrip(container, tmp_path): + path = tmp_path / "test.parquet" + + # 1. Write to parquet + container.to_parquet(path) + assert path.exists() + + # 2. Read back eager + c2 = tdc.TrackingDataContainer.from_parquet(path) + assert c2.schema() == container.schema() + assert len(c2.to_arrow()) == 3 + + # 3. Scan lazy + lazy_c = tdc.scan_parquet(str(path)) + assert isinstance(lazy_c, tdc.LazyTrackingDataContainer) + assert lazy_c.schema() == container.schema() + + # 4. Collect and verify + c3 = lazy_c.collect() + assert c3.schema() == container.schema() + assert c3.to_polars().equals(container.to_polars()) diff --git a/python/tests/test_lazy.py b/python/tests/test_lazy.py new file mode 100644 index 0000000..d6198ed --- /dev/null +++ b/python/tests/test_lazy.py @@ -0,0 +1,160 @@ +from datetime import datetime, timedelta, timezone + +import pytest +import tracking_data_container as tdc + + +class TestDataFusionBackend: + def test_container_lazy_collect(self, parquet_file) -> None: + lazy_c = tdc.scan_parquet(parquet_file) + assert isinstance(lazy_c, tdc.LazyTrackingDataContainer) + + # Collect back to eager + eager_c = lazy_c.collect() + assert isinstance(eager_c, tdc.TrackingDataContainer) + assert eager_c.num_rows() == 3 + + def test_container_lazy_ops(self, parquet_file) -> None: + lazy_c = tdc.scan_parquet(parquet_file) + + # 1. Lazy select + subset_lazy = lazy_c.select("ball/x") + assert isinstance(subset_lazy, tdc.LazyTrackingDataContainer) + assert len(subset_lazy.schema()) == 1 + + # 2. Lazy selector attribute + sel = lazy_c.selector.players("home") + assert isinstance(sel, tdc.selector.Selection) + + # 3. Lazy add column (raw arrays) + new_values = [1.0, 2.0, 3.0] + with pytest.raises(tdc.exceptions.TrackingDataContainerError, match="not yet supported"): + lazy_c.add_column("test/lazy", new_values) + + def test_container_lazy_add_column_callable(self, parquet_file) -> None: + lazy_c = tdc.scan_parquet(parquet_file) + + def compute_sum(x, y): + import polars as pl + + return pl.Series(x.to_numpy() + y.to_numpy()) + + lazy_c = lazy_c.add_column("ball/sum", compute_sum, on=["ball/x", "ball/y"]) + res = lazy_c.collect() + + assert "ball/sum" in res.to_polars().columns + res_list = res.to_polars()["ball/sum"].to_list() + assert res_list[0] == pytest.approx(62.0) + assert res_list[1] == pytest.approx(64.0) + assert res_list[2] == pytest.approx(66.0) + + def test_container_lazy_true_lazy_ops(self, parquet_file) -> None: + # Test things that ARE supported lazily (expressions) + lazy_c = tdc.scan_parquet(parquet_file) + + # Expression-based addition + expr = tdc.col("ball/x").mul(2) + lazy_c = lazy_c.add_column("ball/x2", expr) + + # Filter (use threshold that definitely excludes some rows) + # Sample ball/x are [50.0, 51.0, 52.0] + lazy_c = lazy_c.filter(tdc.col("ball/x").gt(51.5)) + + # Collect + res = lazy_c.collect() + assert "ball/x2" in res.to_polars().columns + assert res.num_rows() == 1 + + def test_container_lazy_transformations(self, parquet_file) -> None: + # 1. Lazy transform coordinates + sys = tdc.CoordinateSystem( + x_range=(0, 100), y_range=(0, 50), length_unit="meters", origin="top_left" + ) + container = tdc.TrackingDataContainer.from_parquet(parquet_file).with_coordinate_system(sys) + + target_sys = tdc.CoordinateSystem( + x_range=(-50, 50), y_range=(-25, 25), length_unit="meters", origin="center" + ) + + lazy_c = container.lazy().transform_coordinates(target_sys) + res = lazy_c.collect() + + # Input ball/x was [50.0, 51.0, 52.0] in (0, 100) range + # Output should be in (-50, 50) range -> [0.0, 1.0, 2.0] + assert res.to_polars()["ball/x"].to_list() == pytest.approx([0.0, 1.0, 2.0]) + + # 2. Lazy transform orientation + # Setup container with known state + container = container.with_orientation(tdc.OrientationMode.HomeAway) + # Period values in sample data: ['first_half', 'second_half', 'first_half'] + # HomeAway means flip in second half. + # Flip across center of (0, 100) range: x -> 100 - x + lazy_c = container.lazy().transform_orientation(tdc.OrientationMode.AwayHome) + res = lazy_c.collect() + + # AwayHome means flip in first half. + # We changed from HomeAway (flip 2nd) to AwayHome (flip 1st). + # So frames 0, 1, 2: + # Frame 0: first_half. HomeAway (no flip) -> AwayHome (flip). CHANGE. + # Frame 1: second_half. HomeAway (flip) -> AwayHome (no flip). CHANGE. + # Frame 2: first_half. HomeAway (no flip) -> AwayHome (flip). CHANGE. + # All frames should be flipped. + assert res.to_polars()["ball/x"][0] == pytest.approx(100.0 - 50.0) + assert res.to_polars()["ball/x"][1] == pytest.approx(100.0 - 51.0) + assert res.to_polars()["ball/x"][2] == pytest.approx(100.0 - 52.0) + + def test_lazy_schema_caching(self, parquet_file): + """Test that lazy schema caching works and propagates.""" + lazy = tdc.scan_parquet(parquet_file) + + # First call reconstructions schema + lazy.schema() + assert getattr(lazy._backend, "_cached_schema", None) is not None + + # Transformation should propagate/subset cache + selected = lazy.select(["ball/x", "ball/y"]) + assert getattr(selected._backend, "_cached_schema", None) is not None + assert len(selected._backend._cached_schema.names()) == 2 + + # Slice should preserve cache + sliced = selected.slice(0, 1) + assert sliced._backend._cached_schema == selected._backend._cached_schema + + +class TestIcebergBackend: + def test_iceberg_lazy_loading(self, iceberg_catalog): + # Load lazily from Iceberg + lazy_loaded = iceberg_catalog.load_container(match_id="match_12345") + assert isinstance(lazy_loaded, tdc.LazyTrackingDataContainer) + + # Test filter + filtered_lazy = lazy_loaded.filter(tdc.col("ball/x").gt(50.5)) + + # Test lazy op (select) + subset_lazy = filtered_lazy.select("ball/x") + + # Collect back to eager + eager_c = subset_lazy.collect() + + assert eager_c.num_rows() == 2 + assert "ball/x" in eager_c.to_polars().columns + assert "ball/y" not in eager_c.to_polars().columns + assert eager_c.to_polars()["ball/x"][0] == 51.0 + assert eager_c.to_polars()["ball/x"][1] == 52.0 + + +def test_materialize_frame_temporal(container): + """Test materializing frames by datetime and timedelta.""" + base_time = datetime(2023, 10, 1, 12, 0, 0, tzinfo=timezone.utc) + + # 1. By datetime + # Frame 1 timestamp is base_time + 40ms = 1696161600040000 microseconds + dt = base_time + timedelta(milliseconds=40) + frame_dt = container.materialize_frame(dt) + assert frame_dt["frame_id"] == 123457 + + # 2. By timedelta + # 40ms offset should be Frame 1. + td = timedelta(milliseconds=40) + frame_td = container.materialize_frame(td) + assert frame_td["frame_id"] == 123457 diff --git a/python/tests/test_ops.py b/python/tests/test_ops.py new file mode 100644 index 0000000..b4ef388 --- /dev/null +++ b/python/tests/test_ops.py @@ -0,0 +1,73 @@ +import numpy as np +import pyarrow as pa +import pytest +import tracking_data_container as tdc + + +def test_compute_derivatives(): + data = { + "ball/x": [0.0, 1.0, 3.0, 6.0], + "ball/y": [0.0, 0.0, 0.0, 0.0], + } + container = tdc.TrackingDataContainer(data, validate=False) + df = container.to_polars() + + # 1. Speed (dt=1.0) + # displacements: 1.0, 2.0, 3.0 + # speed: 0.0, 1.0, 2.0, 3.0 + x_arr = pa.array(df["ball/x"]) + y_arr = pa.array(df["ball/y"]) + speed = tdc.ops.compute_speed(x_arr, y_arr, dt=1.0) + speed_vals = pa.array(speed).to_pylist() + assert speed_vals == [0.0, 1.0, 2.0, 3.0] + + # 2. Acceleration (dt=1.0) + # speed diffs: 1.0, 1.0, 1.0 + # acc: 0.0, 1.0, 1.0, 1.0 + acc = tdc.ops.compute_acceleration(speed, dt=1.0) + acc_vals = pa.array(acc).to_pylist() + assert acc_vals == [0.0, 1.0, 1.0, 1.0] + + # 3. Jerk (dt=1.0) + # acc diffs: 1.0, 0.0, 0.0 + # jerk: 0.0, 1.0, 0.0, 0.0 + jerk = tdc.ops.compute_jerk(acc, dt=1.0) + jerk_vals = pa.array(jerk).to_pylist() + assert jerk_vals == [0.0, 1.0, 0.0, 0.0] + + +def test_compute_angular_velocity(): + # Velocity vector rotating 90 degrees (PI/2) + vx = pa.array([1.0, 0.0]) + vy = pa.array([0.0, 1.0]) + + # dt = 1.0 + # theta: 0, PI/2 + # d_theta: PI/2 + # omega: PI/2 + omega = tdc.ops.compute_angular_velocity(vx, vy, dt=1.0) + omega_vals = pa.array(omega).to_pylist() + assert omega_vals[1] == pytest.approx(np.pi / 2) + + +def test_butterworth_filter(): + import pyarrow as pa + + # Constant signal with noise + data = [10.0] * 50 + # Add some noise + for i in range(len(data)): + if i % 2 == 0: + data[i] += 0.5 + else: + data[i] -= 0.5 + + arr = pa.array(data) + + # Apply filter (low cutoff) + filtered = tdc.ops.butterworth_filter(arr, cutoff=1.0, fs=25.0) + filtered_vals = pa.array(filtered).to_pylist() + + assert len(filtered_vals) == len(data) + # The filter takes some time to settle, check at the end + assert abs(filtered_vals[-1] - 10.0) < 0.1 diff --git a/python/tests/test_schema.py b/python/tests/test_schema.py new file mode 100644 index 0000000..3830b3f --- /dev/null +++ b/python/tests/test_schema.py @@ -0,0 +1,303 @@ +import pytest +import tracking_data_container as tdc + + +class TestDataField: + def test_init_path(self) -> None: + field = tdc.DataField("teams/home/players/1/x") + assert field.name() == "teams/home/players/1/x" + + def test_init_components_player(self) -> None: + field = tdc.DataField(entity=tdc.Player("home", "10"), attribute="x") + assert field.name() == "teams/home/players/10/x" + + def test_init_components_ball(self) -> None: + field = tdc.DataField(entity=tdc.Ball(), attribute="z") + assert field.name() == "ball/z" + + def test_init_components_team(self) -> None: + field = tdc.DataField(entity=tdc.Team("away"), attribute="formation") + assert field.name() == "teams/away/formation" + + def test_init_components_frame(self) -> None: + field = tdc.DataField(attribute="timestamp") + assert field.name() == "timestamp" + + def test_init_vendor(self) -> None: + field = tdc.DataField("vendor/event/name") + assert field.name() == "vendor/event/name" + + field = tdc.DataField(attribute="vendor_event_name") + assert field.name() == "vendor/event/name" + + def test_equality(self) -> None: + f1 = tdc.DataField("ball/x") + f2 = tdc.DataField(entity=tdc.Ball(), attribute="x") + f3 = tdc.DataField("ball/y") + + assert f1 == f2 + assert f1 != f3 + + def test_datafield_subclasses(self) -> None: + frame_field = tdc.DataField(attribute="timestamp") + assert isinstance(frame_field, tdc.FrameDataField) + assert isinstance(frame_field, tdc.DataField) + assert frame_field.attribute == "timestamp" + + entity_field = tdc.DataField(entity=tdc.Player("home", "10"), attribute="x") + assert isinstance(entity_field, tdc.EntityDataField) + assert isinstance(entity_field, tdc.DataField) + assert entity_field.attribute == "x" + assert entity_field.team_id == "home" + assert entity_field.player_id == "10" + + ball_field = tdc.DataField(entity=tdc.Ball(), attribute="z") + assert isinstance(ball_field, tdc.EntityDataField) + assert ball_field.entity.is_ball is True + assert ball_field.attribute == "z" + + def test_tracked_entity_object(self) -> None: + field = tdc.DataField(entity=tdc.Player("away", "7"), attribute="y") + entity = field.entity + + assert isinstance(entity, tdc.Player) + assert isinstance(entity, tdc.TrackedEntity) + assert entity.team_id == "away" + assert entity.player_id == "7" + assert "Player(team=away, id=7)" in repr(entity) + + def test_ball_entity(self) -> None: + field = tdc.DataField(entity=tdc.Ball(), attribute="x") + entity = field.entity + + assert isinstance(entity, tdc.Ball) + assert isinstance(entity, tdc.TrackedEntity) + assert repr(entity) == "Ball" + + def test_team_entity(self) -> None: + field = tdc.DataField(entity=tdc.Team("home"), attribute="formation") + entity = field.entity + + assert isinstance(entity, tdc.Team) + assert isinstance(entity, tdc.TrackedEntity) + assert entity.team_id == "home" + assert "Team(id=home)" in repr(entity) + + def test_referee_entity(self) -> None: + field = tdc.DataField(entity=tdc.Referee("ref_1"), attribute="x") + entity = field.entity + + assert isinstance(entity, tdc.Referee) + assert isinstance(entity, tdc.TrackedEntity) + assert entity.referee_id == "ref_1" + assert "Referee(id=ref_1)" in repr(entity) + + def test_new_constructor(self) -> None: + player = tdc.Player(team_id="home", player_id="10") + field = tdc.DataField(entity=player, attribute="x") + assert field.name() == "teams/home/players/10/x" + assert field.entity == player + + ball = tdc.Ball() + field = tdc.DataField(entity=ball, attribute="z") + assert field.name() == "ball/z" + assert field.entity == ball + + # Test frame attribute with 'attribute' name + field = tdc.DataField(attribute="timestamp") + assert field.name() == "timestamp" + assert isinstance(field, tdc.FrameDataField) + assert field.entity is None + + def test_frame_field_entity_none(self) -> None: + field = tdc.DataField("frame_id") + assert field.entity is None + assert field.team_id is None + assert field.player_id is None + + +class TestFields: + def test_init(self) -> None: + fields = tdc.Fields(["frame_id", "ball/x", "teams/home/players/1/x"]) + assert len(fields) == 3 + assert fields[0].name() == "frame_id" + assert fields[2].name() == "teams/home/players/1/x" + + def test_iteration(self) -> None: + fields = tdc.Fields(["ball/x", "ball/y"]) + names = [f.name() for f in fields] + assert names == ["ball/x", "ball/y"] + + def test_indexing(self) -> None: + fields = tdc.Fields(["ball/x", "ball/y"]) + assert fields[0].name() == "ball/x" + assert fields[1].name() == "ball/y" + + with pytest.raises(IndexError): + _ = fields[2] + + def test_players(self) -> None: + cols = [ + "frame_id", + "teams/home/players/1/x", + "teams/home/players/2/x", + "teams/away/players/10/x", + "ball/x", + ] + fields = tdc.Fields(cols) + + players = fields.players() + assert len(players) == 3 + assert all(isinstance(p, tdc.Player) for p in players) + assert sorted([p.player_id for p in players]) == ["1", "10", "2"] + + home_players = fields.players(team="home") + assert len(home_players) == 2 + assert sorted([p.player_id for p in home_players]) == ["1", "2"] + assert all(p.team_id == "home" for p in home_players) + + away_players = fields.players(team="away") + assert len(away_players) == 1 + assert away_players[0].player_id == "10" + assert away_players[0].team_id == "away" + + assert fields.players(team="other") == [] + + def test_teams(self) -> None: + cols = [ + "teams/home/players/1/x", + "teams/away/players/10/x", + "teams/home/id", + "teams/away/formation", + ] + fields = tdc.Fields(cols) + + teams = fields.teams() + assert len(teams) == 2 + assert all(isinstance(t, tdc.Team) for t in teams) + assert sorted([t.team_id for t in teams]) == ["away", "home"] + + def test_referees(self) -> None: + cols = [ + "referees/ref_1/x", + "referees/ref_2/y", + "ball/x", + ] + fields = tdc.Fields(cols) + + referees = fields.referees() + assert len(referees) == 2 + assert all(isinstance(r, tdc.Referee) for r in referees) + assert sorted([r.referee_id for r in referees]) == ["ref_1", "ref_2"] + + def test_validate_success(self) -> None: + cols = [ + "frame_id", + "timestamp", + "period", + "match/id", + "ball/x", + "ball/y", + "ball/z", + "teams/home/id", + "teams/away/id", + "teams/home/players/1/x", + "teams/home/players/1/y", + "teams/away/players/10/x", + "teams/away/players/10/y", + ] + fields = tdc.Fields(cols) + # Should not raise + fields.validate() + assert fields.validate_quiet() is True + + def test_validate_missing_required(self) -> None: + # Missing ball/z and match/id + cols = [ + "frame_id", + "timestamp", + "period", + "ball/x", + "ball/y", + "teams/home/id", + "teams/away/id", + "teams/home/players/1/x", + "teams/home/players/1/y", + "teams/away/players/10/x", + "teams/away/players/10/y", + ] + fields = tdc.Fields(cols) + + with pytest.raises(tdc.exceptions.SchemaError) as excinfo: + fields.validate() + + assert "ball/z" in str(excinfo.value) + assert "match/id" in str(excinfo.value) + assert fields.validate_quiet() is False + + def test_validate_no_players(self) -> None: + # Teams exist but no players + cols = [ + "frame_id", + "timestamp", + "period", + "match/id", + "ball/x", + "ball/y", + "ball/z", + "teams/home/id", + "teams/away/id", + ] + fields = tdc.Fields(cols) + + with pytest.raises(tdc.exceptions.SchemaError, match="teams/home/players"): + fields.validate() + + def test_validate_player_missing_coord(self) -> None: + # Away player has x but no y + cols = [ + "frame_id", + "timestamp", + "period", + "match/id", + "ball/x", + "ball/y", + "ball/z", + "teams/home/id", + "teams/away/id", + "teams/home/players/1/x", + "teams/home/players/1/y", + "teams/away/players/10/x", + ] + fields = tdc.Fields(cols) + + with pytest.raises(tdc.exceptions.SchemaError, match="teams/away/players"): + fields.validate() + + def test_unsupported_field(self) -> None: + cols = [ + "frame_id", + "timestamp", + "period", + "match/id", + "ball/x", + "ball/y", + "ball/z", + "teams/home/id", + "teams/away/id", + "teams/home/players/1/x", + "teams/home/players/1/y", + "teams/away/players/10/x", + "teams/away/players/10/y", + "teams/home/xGoal", # Unsupported entity attribute + "dummy", # Unsupported frame attribute + ] + fields = tdc.Fields(cols) + + with pytest.warns(tdc.exceptions.SchemaWarning) as record: + fields.validate() + + msg = str(record[0].message) + assert "teams/home/xGoal" in msg + assert "dummy" in msg + assert fields.validate_quiet() is True diff --git a/python/tests/test_select.py b/python/tests/test_select.py new file mode 100644 index 0000000..36f22a4 --- /dev/null +++ b/python/tests/test_select.py @@ -0,0 +1,97 @@ +import tracking_data_container as tdc + + +class TestSelection: + def test_container_select_string(self, container) -> None: + subset = container.select("ball/x") + assert subset.schema() == tdc.Fields(["ball/x"]) + + def test_container_select_list(self, container) -> None: + subset = container.select(["ball/x", "ball/y"]) + assert subset.schema() == tdc.Fields(["ball/x", "ball/y"]) + + def test_container_select_datafield(self, container) -> None: + field = tdc.DataField("ball/x") + subset = container.select(field) + assert subset.schema() == tdc.Fields(["ball/x"]) + + def test_container_select_selector_ball(self, container) -> None: + subset = container.select(container.selector.ball()) + assert subset.schema() == tdc.Fields(["ball/x", "ball/y", "ball/z"]) + + def test_container_select_selector_players(self, container) -> None: + # Add some player data to test player selection + data = { + "ball/x": [1.0], + "teams/home/players/1/x": [2.0], + "teams/home/players/1/y": [3.0], + "teams/away/players/10/x": [4.0], + } + c = tdc.TrackingDataContainer(data, validate=False) + + subset = c.select(c.selector.players(team_id="home")) + assert subset.schema() == tdc.Fields(["teams/home/players/1/x", "teams/home/players/1/y"]) + + subset = c.select(c.selector.players().attributes(["x"])) + assert subset.schema() == tdc.Fields(["teams/away/players/10/x", "teams/home/players/1/x"]) + + def test_container_select_selector_player(self) -> None: + data = { + "teams/home/players/1/x": [2.0], + "teams/home/players/1/y": [3.0], + "teams/home/players/2/x": [4.0], + "teams/away/players/1/x": [5.0], + } + c = tdc.TrackingDataContainer(data, validate=False) + + # Select specific player by ID and team + subset = c.select(c.selector.player("1", team_id="home")) + assert subset.schema() == tdc.Fields(["teams/home/players/1/x", "teams/home/players/1/y"]) + + # Select specific player by ID only + subset = c.select(c.selector.player("1")) + assert subset.schema() == tdc.Fields( + ["teams/away/players/1/x", "teams/home/players/1/x", "teams/home/players/1/y"] + ) + + def test_container_select_wildcard(self) -> None: + data = { + "ball/x": [1.0], + "ball/y": [1.1], + "teams/home/players/1/x": [2.0], + "teams/home/players/1/y": [3.0], + "teams/home/players/2/x": [4.0], + "teams/away/players/10/x": [5.0], + "teams/away/players/10/y": [5.1], + } + c = tdc.TrackingDataContainer(data, validate=False) + + # 1. Select all x coordinates of the home team using ** + subset = c.select("teams/home/**/x") + assert sorted([f.name() for f in subset.schema()]) == sorted( + ["teams/home/players/1/x", "teams/home/players/2/x"] + ) + + # 2. Select x and y coordinates of the home team using list of ** + subset = c.select(["teams/home/**/x", "teams/home/**/y"]) + assert sorted([f.name() for f in subset.schema()]) == sorted( + ["teams/home/players/1/x", "teams/home/players/2/x", "teams/home/players/1/y"] + ) + + # 3. Select all x coordinates (players and ball) using leading ** + subset = c.select("**/x") + assert sorted([f.name() for f in subset.schema()]) == sorted( + [ + "ball/x", + "teams/home/players/1/x", + "teams/home/players/2/x", + "teams/away/players/10/x", + ] + ) + + # 4. Select x coordinates of home players using combined wildcards + # Using ** to skip segments and * for a single segment + subset = c.select("teams/home/**/players/*/x") + assert sorted([f.name() for f in subset.schema()]) == sorted( + ["teams/home/players/1/x", "teams/home/players/2/x"] + ) diff --git a/python/tests/test_transform.py b/python/tests/test_transform.py new file mode 100644 index 0000000..3a270e6 --- /dev/null +++ b/python/tests/test_transform.py @@ -0,0 +1,189 @@ +import pytest +import tracking_data_container as tdc + + +class TestTransformations: + def test_transform_coordinates(self) -> None: + data = { + "ball/x": [0.0, 50.0, 100.0], + "ball/y": [0.0, 50.0, 100.0], + } + + # Define source system (Normalized 0-100) + source_sys = tdc.CoordinateSystem( + x_range=(0.0, 100.0), + y_range=(0.0, 100.0), + origin="bottom_left", + x_direction="right", + y_direction="up", + ) + + container = tdc.TrackingDataContainer(data, validate=False).with_coordinate_system( + source_sys + ) + + # Define target system (Metric -50 to 50) + target_sys = tdc.CoordinateSystem( + x_range=(-50.0, 50.0), + y_range=(-50.0, 50.0), + origin="center", + x_direction="right", + y_direction="up", + ) + + transformed = container.transform_coordinates(target_sys) + + df = transformed.to_polars() + # 0.0 -> -50.0 + # 50.0 -> 0.0 + # 100.0 -> 50.0 + res = df["ball/x"].to_list() + assert res[0] == pytest.approx(-50.0) + assert res[1] == pytest.approx(0.0) + assert res[2] == pytest.approx(50.0) + assert transformed.coordinate_system.origin == "center" + + def test_transform_units(self) -> None: + data = {"ball/x": [1.0], "ball/y": [1.0]} # 1 meter + + source_sys = tdc.CoordinateSystem(length_unit="meters", x_range=(0, 100), y_range=(0, 100)) + container = tdc.TrackingDataContainer(data, validate=False).with_coordinate_system( + source_sys + ) + + target_sys = tdc.CoordinateSystem(length_unit="yards", x_range=(0, 100), y_range=(0, 100)) + transformed = container.transform_coordinates(target_sys) + + # 1 meter = 1.09361 yards + df = transformed.to_polars() + assert df["ball/x"][0] == pytest.approx(1.09361, rel=1e-4) + + def test_transform_missing_units_raises(self) -> None: + data = {"ball/x": [1.0]} + container = tdc.TrackingDataContainer(data, validate=False) # No metadata + + target_sys = tdc.CoordinateSystem(length_unit="yards") + + with pytest.raises(tdc.exceptions.InvalidParametersError, match="No coordinate system"): + container.transform_coordinates(target_sys) + + def test_transform_undefined_ranges_raises(self) -> None: + # Define system with origin only, no units or ranges + sys = tdc.CoordinateSystem(origin="center") + container = tdc.TrackingDataContainer( + {"ball/x": [1.0]}, validate=False + ).with_coordinate_system(sys) + + target_sys = tdc.CoordinateSystem(length_unit="yards") + + with pytest.raises(tdc.exceptions.InvalidParametersError, match="Source X range undefined"): + container.transform_coordinates(target_sys) + + def test_transform_orientation(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "first_half"], + "ball_poss_team_id": ["home", "away"], + } + + # Source is static, we want to normalize it + container = tdc.TrackingDataContainer(data, validate=False, orientation="static_home_away") + + # In "ball_owning_team" mode, if "away" has possession, we flip + transformed = container.transform_orientation("ball_owning_team") + + df = transformed.to_polars() + # Frame 1: home possession -> no flip -> 10.0 + # Frame 2: away possession -> flip -> -10.0 + assert df["ball/x"].to_list() == [10.0, -10.0] + + def test_transform_orientation_home_away(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "second_half"], + } + container = tdc.TrackingDataContainer(data, validate=False, orientation="static_home_away") + + # HomeAway: second_half flips + transformed = container.transform_orientation("home_away") + assert transformed.to_polars()["ball/x"].to_list() == [10.0, -10.0] + + def test_transform_orientation_away_home(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "second_half"], + } + container = tdc.TrackingDataContainer(data, validate=False, orientation="static_home_away") + + # AwayHome: first_half flips + transformed = container.transform_orientation("away_home") + assert transformed.to_polars()["ball/x"].to_list() == [-10.0, 10.0] + + def test_transform_orientation_identity(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "second_half"], + } + container = tdc.TrackingDataContainer(data, validate=False).with_orientation( + tdc.OrientationMode.AwayHome + ) + + # Transforming to the same mode should be a no-op + transformed = container.transform_orientation(tdc.OrientationMode.AwayHome) + assert transformed.orientation_mode == tdc.OrientationMode.AwayHome + assert transformed.to_polars()["ball/x"].to_list() == [10.0, 10.0] + + def test_transform_orientation_static_to_dynamic(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "second_half"], + } + # Data is already in AwayHome orientation but marked as StaticHomeAway + container = tdc.TrackingDataContainer(data, validate=False).with_orientation( + tdc.OrientationMode.StaticHomeAway + ) + + # Transform to AwayHome: + # should_flip(StaticHomeAway) is always False + # should_flip(AwayHome) is True for first_half, False for second_half + # Result: first_half flips (False != True), second_half stays (False != False) + transformed = container.transform_orientation(tdc.OrientationMode.AwayHome) + assert transformed.to_polars()["ball/x"].to_list() == [-10.0, 10.0] + + def test_transform_orientation_dynamic_to_static(self) -> None: + data = { + "ball/x": [10.0, 10.0], + "period": ["first_half", "second_half"], + } + # Data is in AwayHome orientation + container = tdc.TrackingDataContainer(data, validate=False).with_orientation( + tdc.OrientationMode.AwayHome + ) + + # Transform to StaticHomeAway (which never flips) + # first_half: should_flip(AwayHome)=True, should_flip(StaticHomeAway)=False -> Flip + # second_half: should_flip(AwayHome)=False, should_flip(StaticHomeAway)=False -> No Flip + transformed = container.transform_orientation(tdc.OrientationMode.StaticHomeAway) + assert transformed.to_polars()["ball/x"].to_list() == [-10.0, 10.0] + + def test_transform_orientation_with_coordinate_system(self) -> None: + data = { + "ball/x": [10.0], + "period": ["first_half"], + } + raw_cs = tdc.CoordinateSystem( + x_range=(0, 100), + y_range=(0, 100), + origin=tdc.Origin.TopLeft, + ) + container = ( + tdc.TrackingDataContainer(data, validate=False) + .with_coordinate_system(raw_cs) + .with_orientation(tdc.OrientationMode.AwayHome) + ) + + # AwayHome first_half flips. Transforming to StaticHomeAway (no flip) + # should trigger a flip from the AwayHome state. + # Flip across center of (0, 100) means 10.0 -> 90.0 + transformed = container.transform_orientation(tdc.OrientationMode.StaticHomeAway) + assert transformed.to_polars()["ball/x"][0] == pytest.approx(90.0) diff --git a/python/tracking_data_container/__init__.py b/python/tracking_data_container/__init__.py new file mode 100644 index 0000000..c77bf8f --- /dev/null +++ b/python/tracking_data_container/__init__.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import logging +import warnings + +from . import exceptions +from ._tracking_data_container import ( + Attr, + Direction, + Expr, + Frame, + LengthUnit, + OrientationMode, + Origin, + __version__, + col, + lit, + ops, +) +from .builder import TrackingDataBuilder +from .catalogs.base import Catalog +from .catalogs.iceberg import IcebergConfig, IcebergTrackingDataCatalog +from .container import LazyTrackingDataContainer, TrackingDataContainer +from .coordinate_system import CoordinateSystem +from .io import load_hawkeye, load_sportec, load_statsperform, scan_parquet +from .schema import ( + Ball, + DataField, + EntityDataField, + Fields, + FrameDataField, + Player, + Referee, + SchemaId, + Team, + TrackedEntity, + list_schemas, +) +from .selector import ColumnSelector, Selection + +TrackingDataCatalog = IcebergTrackingDataCatalog + +# Silence noisy PyIceberg warnings and logs +warnings.filterwarnings("ignore", category=UserWarning, module="pyiceberg.*") +warnings.filterwarnings("ignore", message=".*Delete operation did not match any records.*") +logging.getLogger("pyiceberg").setLevel(logging.ERROR) + +__all__ = ( + "__version__", + "TrackingDataContainer", + "LazyTrackingDataContainer", + "TrackingDataBuilder", + "TrackingDataCatalog", + "IcebergTrackingDataCatalog", + "Catalog", + "IcebergConfig", + "CoordinateSystem", + "Origin", + "Direction", + "LengthUnit", + "OrientationMode", + "ColumnSelector", + "Selection", + "scan_parquet", + "load_sportec", + "load_statsperform", + "load_hawkeye", + "DataField", + "TrackedEntity", + "Ball", + "Player", + "Team", + "Referee", + "FrameDataField", + "EntityDataField", + "SchemaId", + "list_schemas", + "Expr", + "Attr", + "Fields", + "Frame", + "col", + "lit", + "ops", + "exceptions", +) diff --git a/python/tracking_data_container/_tracking_data_container.pyi b/python/tracking_data_container/_tracking_data_container.pyi new file mode 100644 index 0000000..6b46ae2 --- /dev/null +++ b/python/tracking_data_container/_tracking_data_container.pyi @@ -0,0 +1,325 @@ +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Any, Callable + +if TYPE_CHECKING: + import pyarrow as pa + +__version__: str +CORE_COLUMNS: list[str] + +class Attr: + """Standardized attribute names for tracking data.""" + + FrameId: Attr + Timestamp: Attr + Period: Attr + BallStatus: Attr + BallPossTeamId: Attr + BallPossStatus: Attr + X: Attr + Y: Attr + Z: Attr + Vel: Attr + Acc: Attr + Dist: Attr + @property + def value(self) -> str: ... + +class _TrackedEntity: + """ + Internal representation of a tracked entity. + """ + def __init__( + self, + team_id: str | None = None, + player_id: str | None = None, + referee_id: str | None = None, + ball: bool | None = None, + ) -> None: ... + def team_id(self) -> str | None: ... + def player_id(self) -> str | None: ... + def is_ball(self) -> bool: ... + def is_player(self) -> bool: ... + def is_team(self) -> bool: ... + def is_referee(self) -> bool: ... + +class _DataField: + """ + Internal representation of a data column. + """ + def __init__( + self, + path: str | None = None, + *, + attribute: Attr | str | None = None, + entity: _TrackedEntity | None = None, + ) -> None: ... + @staticmethod + def from_name(name: str) -> _DataField: ... + def team_id(self) -> str | None: ... + def player_id(self) -> str | None: ... + def is_frame(self) -> bool: ... + def is_entity(self) -> bool: ... + def entity(self) -> _TrackedEntity: ... + def attribute(self) -> str: ... + def name(self) -> str: ... + +class _Fields: + """Collection of DataFields representing a schema.""" + def __init__(self, columns: list[str]) -> None: ... + def __getitem__(self, index: int) -> _DataField: ... + def __len__(self) -> int: ... + def __iter__(self) -> _Fields: ... + def __next__(self) -> _DataField: ... + def player_entities(self, team: str | None = None) -> list[_TrackedEntity]: ... + def team_entities(self) -> list[_TrackedEntity]: ... + def referee_entities(self) -> list[_TrackedEntity]: ... + def validate(self) -> None: ... + def validate_quiet(self) -> bool: ... + def validate_with_schema(self, schema_id: _SchemaId) -> None: ... + +class _SchemaId: + """Internal representation of a schema identifier.""" + def __init__(self, sport: str, data_kind: str, version: str) -> None: ... + @staticmethod + def from_string(s: str) -> _SchemaId: ... + @property + def sport(self) -> str: ... + @property + def data_kind(self) -> str: ... + @property + def version(self) -> str: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + +def list_schemas() -> list[str]: ... + +class Origin: + """Possible origin locations for a coordinate system.""" + + Center: Origin + TopLeft: Origin + BottomLeft: Origin + +class Direction: + """Possible axis directions for a coordinate system.""" + + Right: Direction + Left: Direction + Up: Direction + Down: Direction + +class LengthUnit: + """Standardized units of length.""" + + Meters: LengthUnit + Centimeters: LengthUnit + Millimeters: LengthUnit + Yards: LengthUnit + Inches: LengthUnit + Feet: LengthUnit + Percent: LengthUnit + +class OrientationMode: + """Normalization modes for playing direction.""" + + BallOwningTeam: OrientationMode + HomeAway: OrientationMode + AwayHome: OrientationMode + StaticHomeAway: OrientationMode + StaticAwayHome: OrientationMode + +class _CoordinateSystem: + """Internal representation of a coordinate system.""" + def __init__( + self, + x_range: tuple[float, float] | None = None, + y_range: tuple[float, float] | None = None, + origin: Origin | str = "center", + x_direction: Direction | str = "right", + y_direction: Direction | str = "up", + length_unit: LengthUnit | str | None = None, + ) -> None: ... + @property + def x_range(self) -> tuple[float, float] | None: ... + @property + def y_range(self) -> tuple[float, float] | None: ... + @property + def origin(self) -> str: ... + @property + def x_direction(self) -> str: ... + @property + def y_direction(self) -> str: ... + @property + def length_unit(self) -> str | None: ... + +class Selection: + """Internal selection.""" + @property + def expr(self) -> str: ... + def attributes(self, names: list[str]) -> Selection: ... + +class ColumnSelector: + """A selector for building column expressions based on metadata.""" + def ball(self) -> Selection: ... + def players(self, team_id: str | None = None) -> Selection: ... + def player(self, player_id: str, team_id: str | None = None) -> Selection: ... + def team(self, team_id: str) -> Selection: ... + +class Frame: + """A single frame of tracking data.""" + def __getitem__(self, key: str | _DataField) -> Any: ... + def to_dict(self) -> dict[_DataField, Any]: ... + +class _TrackingDataContainer: + """Eager tracking data container (internal).""" + def __init__( + self, + data: dict[str, list[Any]], + coordinate_system: _CoordinateSystem | None = None, + orientation: str | None = None, + ) -> None: ... + @classmethod + def _from_capsule(cls, capsule: Any) -> _TrackingDataContainer: ... + @staticmethod + def pivot( + data: Any, entity_to_team: dict[str, str] | None = None + ) -> _TrackingDataContainer: ... + @staticmethod + def from_arrow( + array_capsule: Any, + schema_capsule: Any, + coordinate_system: _CoordinateSystem | None = None, + orientation: str | None = None, + ) -> _TrackingDataContainer: ... + def lazy(self) -> _LazyTrackingDataContainer: ... + def select( + self, items: str | list[str | _DataField | Selection] | _DataField | Selection + ) -> _TrackingDataContainer: ... + def add_column( + self, name: str, value: Any, _schema_capsule: Any = None + ) -> _TrackingDataContainer: ... + def add_columns( + self, names: list[str], values: list[Any], _schema_capsules: list[Any] | None = None + ) -> _TrackingDataContainer: ... + def with_coordinate_system(self, system: _CoordinateSystem) -> _TrackingDataContainer: ... + def transform_coordinates(self, to_system: _CoordinateSystem) -> _TrackingDataContainer: ... + def transform_orientation(self, mode: OrientationMode | str) -> _TrackingDataContainer: ... + def with_orientation(self, mode: OrientationMode | str) -> _TrackingDataContainer: ... + @property + def schema(self) -> _Fields: ... + @property + def coordinate_system(self) -> _CoordinateSystem | None: ... + @property + def orientation_mode(self) -> OrientationMode | None: ... + @property + def selector(self) -> ColumnSelector: ... + def num_rows(self) -> int: ... + def slice(self, offset: int, length: int) -> _TrackingDataContainer: ... + def validate(self) -> None: ... + def validate_with_schema(self, schema_id: _SchemaId) -> None: ... + @property + def schema_id(self) -> _SchemaId | None: ... + def with_schema_id(self, schema_id: _SchemaId) -> _TrackingDataContainer: ... + def save_parquet(self, path: str) -> None: ... + @staticmethod + def load_parquet(path: str) -> _TrackingDataContainer: ... + def materialize_frame(self, frame: int | datetime | Any) -> Frame: ... + def to_arrow(self) -> pa.RecordBatch: ... + def __arrow_c_schema__(self) -> object: ... + def __arrow_c_array__(self, requested_schema: object = None) -> tuple[object, object]: ... + +class _LazyTrackingDataContainer: + """Lazy tracking data container (internal).""" + def collect(self) -> _TrackingDataContainer: ... + def select( + self, items: str | list[str | _DataField | Selection] | _DataField | Selection + ) -> _LazyTrackingDataContainer: ... + def slice(self, offset: int, length: int) -> _LazyTrackingDataContainer: ... + def filter(self, predicate: Expr) -> _LazyTrackingDataContainer: ... + def transform_coordinates(self, to_system: _CoordinateSystem) -> _LazyTrackingDataContainer: ... + def transform_orientation(self, mode: OrientationMode | str) -> _LazyTrackingDataContainer: ... + def add_column( + self, name: str, value: Any, _schema_capsule: Any = None + ) -> _LazyTrackingDataContainer: ... + def add_columns( + self, names: list[str], values: list[Any], _schema_capsules: list[Any] | None = None + ) -> _LazyTrackingDataContainer: ... + def add_column_udf( + self, name: str, func: Callable, on: list[str] + ) -> _LazyTrackingDataContainer: ... + @property + def schema(self) -> _Fields: ... + @property + def coordinate_system(self) -> _CoordinateSystem | None: ... + @property + def orientation_mode(self) -> OrientationMode | None: ... + @property + def selector(self) -> ColumnSelector: ... + def __arrow_c_schema__(self) -> object: ... + def __arrow_c_stream__(self, requested_schema: object = None) -> object: ... + +class _TrackingDataBuilder: + """Internal tracking data builder.""" + def __init__(self) -> None: ... + def insert_int(self, frame_id: int, col: str | _DataField, val: int) -> None: ... + def insert_float(self, frame_id: int, col: str | _DataField, val: float) -> None: ... + def insert_string(self, frame_id: int, col: str | _DataField, val: str) -> None: ... + def insert_bool(self, frame_id: int, col: str | _DataField, val: bool) -> None: ... + def merge(self, other: _TrackingDataBuilder) -> None: ... + def build(self) -> _TrackingDataContainer: ... + +class Expr: + """A logical expression for selecting or transforming data.""" + def __add__(self, other: Expr | float | int) -> Expr: ... + def __sub__(self, other: Expr | float | int) -> Expr: ... + def __mul__(self, other: Expr | float | int) -> Expr: ... + def __truediv__(self, other: Expr | float | int) -> Expr: ... + def __and__(self, other: Expr) -> Expr: ... + def __or__(self, other: Expr) -> Expr: ... + def eq(self, other: Expr | Any) -> Expr: ... + def ne(self, other: Expr | Any) -> Expr: ... + def gt(self, other: Expr | Any) -> Expr: ... + def ge(self, other: Expr | Any) -> Expr: ... + def lt(self, other: Expr | Any) -> Expr: ... + def le(self, other: Expr | Any) -> Expr: ... + def diff(self) -> Expr: ... + def pow(self, exponent: float) -> Expr: ... + def sqrt(self) -> Expr: ... + def replace_columns(self, mapping: dict[str, str]) -> Expr: ... + +def col(name: str) -> Expr: ... +def lit(value: Any) -> Expr: ... +def load_sportec(path: str, meta_path: str | None = None) -> Any: ... +def load_statsperform(path: str) -> Any: ... +def load_hawkeye( + ball_paths: list[str], + centroid_paths: list[str], + joint_paths: list[str] | None = None, + meta_path: str | None = None, +) -> Any: ... +def scan_parquet(path: str) -> _LazyTrackingDataContainer: ... + +class _Ops: + @staticmethod + def compute_speed(x_array: Any, y_array: Any, dt: float = 1.0) -> Any: ... + @staticmethod + def compute_acceleration(speed_array: Any, dt: float = 1.0) -> Any: ... + @staticmethod + def compute_jerk(acc_array: Any, dt: float = 1.0) -> Any: ... + @staticmethod + def compute_angular_velocity(vx_array: Any, vy_array: Any, dt: float = 1.0) -> Any: ... + @staticmethod + def butterworth_filter(array: Any, cutoff: float, fs: float, order: int = 2) -> Any: ... + +ops: _Ops + +# Exceptions +class TrackingDataContainerError(Exception): ... +class InvalidParametersError(TrackingDataContainerError): ... +class SchemaError(TrackingDataContainerError): ... +class SchemaWarning(UserWarning): ... diff --git a/python/tracking_data_container/backends/__init__.py b/python/tracking_data_container/backends/__init__.py new file mode 100644 index 0000000..57bf3ed --- /dev/null +++ b/python/tracking_data_container/backends/__init__.py @@ -0,0 +1,5 @@ +from .base import LazyBackend +from .datafusion import DataFusionBackend +from .iceberg import IcebergBackend + +__all__ = ["LazyBackend", "DataFusionBackend", "IcebergBackend"] diff --git a/python/tracking_data_container/backends/base.py b/python/tracking_data_container/backends/base.py new file mode 100644 index 0000000..c393bb0 --- /dev/null +++ b/python/tracking_data_container/backends/base.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +from .._tracking_data_container import Expr as _Expr +from .._tracking_data_container import OrientationMode +from ..coordinate_system import CoordinateSystem +from ..schema import Fields + +if TYPE_CHECKING: + from ..container import TrackingDataContainer + + +class LazyBackend: + """Base interface for lazy loading backends.""" + + def schema(self) -> Fields: + raise NotImplementedError + + def select(self, items: str | Sequence[str]) -> LazyBackend: + raise NotImplementedError + + def add_column( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> LazyBackend: + raise NotImplementedError + + def collect(self) -> TrackingDataContainer: + raise NotImplementedError + + def slice(self, offset: int, length: int) -> LazyBackend: + raise NotImplementedError + + def filter(self, predicate: _Expr) -> LazyBackend: + raise NotImplementedError + + def transform_coordinates(self, sys: CoordinateSystem) -> LazyBackend: + raise NotImplementedError + + def transform_orientation(self, mode: str | OrientationMode) -> LazyBackend: + raise NotImplementedError + + def __arrow_c_stream__(self, rs: Any = None) -> Any: + raise NotImplementedError diff --git a/python/tracking_data_container/backends/datafusion.py b/python/tracking_data_container/backends/datafusion.py new file mode 100644 index 0000000..3967934 --- /dev/null +++ b/python/tracking_data_container/backends/datafusion.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +import logging +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +from .._tracking_data_container import ( + CORE_COLUMNS as _CORE_COLUMNS, +) +from .._tracking_data_container import ( + Expr as _Expr, +) +from .._tracking_data_container import ( + OrientationMode, + _DataField, + _LazyTrackingDataContainer, +) +from .._tracking_data_container import ( + Selection as _Selection, +) +from ..coordinate_system import CoordinateSystem +from ..exceptions import TrackingDataContainerError +from ..schema import DataField, Fields +from ..selector import Selection +from .base import LazyBackend + +if TYPE_CHECKING: + from ..container import TrackingDataContainer + +logger = logging.getLogger(__name__) + +# Constants for performance (shared with container.py) +CORE_COLUMNS = set(_CORE_COLUMNS) + + +class DataFusionBackend(LazyBackend): + """DataFusion backend for lazy evaluation (e.g. Parquet scanning).""" + + def __init__( + self, + container: _LazyTrackingDataContainer, + cached_schema: Fields | None = None, + deferred_add_columns: list[tuple] | None = None, + ): + self._container = container + self._cached_schema = cached_schema + self._deferred_add_columns = deferred_add_columns or [] + + def schema(self) -> Fields: + if self._cached_schema: + return self._cached_schema + self._cached_schema = Fields(self._container.schema) + return self._cached_schema + + def select(self, items: str | Sequence[str]) -> LazyBackend: + inputs = items if isinstance(items, (list, tuple)) else [items] + processed: list[str | _DataField | _Selection] = [] + for i in inputs: + if isinstance(i, str): + if "*" in i: + for m in self.schema().match(i): + processed.append(_DataField.from_name(m)) + else: + processed.append(_DataField.from_name(i)) + elif isinstance(i, DataField): + processed.append(i._field) + elif isinstance(i, Selection): + processed.append(i._inner) + elif isinstance(i, (_DataField, _Selection)): + processed.append(i) + + new_cache = None + if self._cached_schema: + names = [] + for itm in inputs: + if isinstance(itm, str): + names.extend(self._cached_schema.match(itm)) + else: + names.append(str(itm)) + new_cache = Fields(list(dict.fromkeys(names))) + + return DataFusionBackend( + self._container.select(processed), + cached_schema=new_cache, + deferred_add_columns=self._deferred_add_columns, + ) + + def add_column( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> LazyBackend: + if isinstance(value, _Expr): + return DataFusionBackend( + self._container.add_column(name, value), + cached_schema=self._cached_schema, + deferred_add_columns=self._deferred_add_columns, + ) + + if callable(value): + new_deferred = list(self._deferred_add_columns) + new_deferred.append((name, value, on)) + return DataFusionBackend( + self._container, + cached_schema=self._cached_schema, + deferred_add_columns=new_deferred, + ) + + raise TrackingDataContainerError( + "Adding non-expression columns to lazy containers is not yet supported." + ) + + def collect(self) -> TrackingDataContainer: + from ..container import TrackingDataContainer + + try: + inner = self._container.collect() + res = TrackingDataContainer(inner, validate=False) + + for name, value, on in self._deferred_add_columns: + res = res.add_column(name, value, on=on) + + return res + except Exception as e: + raise TrackingDataContainerError(f"Collection failed: {e}") + + def slice(self, offset: int, length: int) -> LazyBackend: + return DataFusionBackend( + self._container.slice(offset, length), + cached_schema=self._cached_schema, + deferred_add_columns=self._deferred_add_columns, + ) + + def filter(self, predicate: _Expr) -> LazyBackend: + return DataFusionBackend( + self._container.filter(predicate), + cached_schema=self._cached_schema, + deferred_add_columns=self._deferred_add_columns, + ) + + def transform_coordinates(self, sys: CoordinateSystem) -> LazyBackend: + return DataFusionBackend( + self._container.transform_coordinates(sys._inner), + cached_schema=self._cached_schema, + deferred_add_columns=self._deferred_add_columns, + ) + + def transform_orientation(self, mode: str | OrientationMode) -> LazyBackend: + return DataFusionBackend( + self._container.transform_orientation(str(mode)), + cached_schema=self._cached_schema, + deferred_add_columns=self._deferred_add_columns, + ) + + def __arrow_c_stream__(self, rs: Any = None) -> Any: + return self._container.__arrow_c_stream__(rs) diff --git a/python/tracking_data_container/backends/iceberg.py b/python/tracking_data_container/backends/iceberg.py new file mode 100644 index 0000000..b9940d5 --- /dev/null +++ b/python/tracking_data_container/backends/iceberg.py @@ -0,0 +1,311 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, cast + +from pyiceberg.expressions import ( + And, + BooleanExpression, + EqualTo, + Reference, +) + +from .._tracking_data_container import Expr as _Expr +from .._tracking_data_container import OrientationMode +from ..container import CORE_COLUMNS, TrackingDataContainer +from ..coordinate_system import CoordinateSystem +from ..schema import DataField, Fields, TrackedEntity +from .base import LazyBackend + +if TYPE_CHECKING: + from ..catalogs.iceberg import IcebergTrackingDataCatalog + + +class IcebergBackend(LazyBackend): + """A lazy container backend that defers loading from an Iceberg catalog.""" + + def __init__( + self, + catalog: IcebergTrackingDataCatalog, + match_id: str, + player_id: str | None = None, + selection: str | Sequence[str] | None = None, + limit: int | None = None, + **filters: Any, + ): + self._catalog: IcebergTrackingDataCatalog = catalog + self._match_id = match_id + self._player_id = player_id + self._selection = selection + self._limit = limit + self._filters = filters + self._cached_schema: Fields | None = None + self._predicates: list[_Expr] = [] + + def _get_entities_from_metadata( + self, hierarchical_entities: dict[str, Any] + ) -> list[tuple[TrackedEntity, dict[str, Any]]]: + """Flatten hierarchical metadata into a list of (TrackedEntity, attributes) tuples.""" + entities: list[tuple[TrackedEntity, dict[str, Any]]] = [] + + if ball_attrs := hierarchical_entities.get("ball"): + entities.append((TrackedEntity.from_entity_key("ball|none"), ball_attrs)) + + for tid, team_data in hierarchical_entities.get("teams", {}).items(): + team_attrs = {k: v for k, v in team_data.items() if k != "players"} + entities.append((TrackedEntity.from_entity_key(f"team:{tid}|{tid}"), team_attrs)) + for pid, p_attrs in team_data.get("players", {}).items(): + entities.append((TrackedEntity.from_entity_key(f"{pid}|{tid}"), p_attrs)) + + for rid, r_attrs in hierarchical_entities.get("referees", {}).items(): + entities.append((TrackedEntity.from_entity_key(f"referee:{rid}|none"), r_attrs)) + + if self._player_id: + entities = [ + (e, attrs) + for e, attrs in entities + if e.player_id == self._player_id + or (e.is_referee and e.referee_id == self._player_id) + ] + + return entities + + def _get_wide_names_for_entity( + self, entity: TrackedEntity, attributes: dict[str, Any], physical_attr_cols: list[str] + ) -> list[str]: + """Generate all wide CDF field names for a specific entity.""" + prefix = entity.cdf_prefix() + names = [] + for attr in physical_attr_cols: + names.append(f"{prefix}{attr}") + for attr in attributes: + names.append(f"{prefix}{attr}") + if not entity.is_ball: + names.append(f"{prefix}player/id") + if entity.is_player or entity.is_team: + names.append(f"{prefix}team/id") + return names + + def schema(self) -> Fields: + """Reconstruct the wide schema by inspecting entities in the match metadata.""" + if self._cached_schema: + return self._cached_schema + + if self._catalog._tracking_table is None: + self._catalog._tracking_table = self._catalog._iceberg_catalog.load_table( + "tracking.tracking_data" + ) + + metadata = self._catalog.get_metadata(self._match_id) + entities_hier = metadata.get("_entities") + if entities_hier is None: + raise ValueError(f"Metadata for match {self._match_id} is missing unique entity list.") + + physical_names = {f.name for f in self._catalog._tracking_table.schema().fields} + ignored = { + "match_id", + "competition_id", + "season_id", + "timestamp", + "frame_id", + "period", + "period_id", + "entity_type", + "entity_id", + "entity_key", + } + attr_cols = [c for c in physical_names if c not in ignored and c != "id"] + + wide_names = [ + c + for c in CORE_COLUMNS + if c not in ("player/id", "team/id", "entity_key") + and DataField(c).to_iceberg_name() in physical_names + ] + + for entity, attrs in self._get_entities_from_metadata(entities_hier): + wide_names.extend(self._get_wide_names_for_entity(entity, attrs, attr_cols)) + + self._cached_schema = Fields(list(dict.fromkeys(wide_names))) + return self._cached_schema + + def select(self, items: str | Sequence[str]) -> IcebergBackend: + """Translate wide selection to long selection pushdown.""" + backend = IcebergBackend( + self._catalog, + self._match_id, + self._player_id, + selection=items, + limit=self._limit, + **self._filters, + ) + backend._predicates = list(self._predicates) + return backend + + def slice(self, offset: int, length: int) -> IcebergBackend: + """Apply limit to the lazy container.""" + backend = IcebergBackend( + self._catalog, + self._match_id, + self._player_id, + selection=self._selection, + limit=length, + **self._filters, + ) + backend._predicates = list(self._predicates) + return backend + + def collect(self) -> TrackingDataContainer: + """Finally perform the Iceberg scan and pivot.""" + if self._catalog._tracking_table is None: + self._catalog._tracking_table = self._catalog._iceberg_catalog.load_table( + "tracking.tracking_data" + ) + + filter_expr: BooleanExpression = EqualTo( + term=Reference("match_id"), value=cast(Any, self._match_id) + ) # type: ignore + if self._player_id: + filter_expr = And( + filter_expr, + EqualTo(term=Reference("entity_id"), value=cast(Any, self._player_id)), # type: ignore + ) + for k, v in self._filters.items(): + filter_expr = And( + filter_expr, + EqualTo(term=Reference(k), value=cast(Any, v)), # type: ignore + ) + + # 1. Column Pushdown Calculation + selected_long_fields = None + deferred_select = None + metadata = self._catalog.get_metadata(self._match_id) + entities_hier = metadata.get("_entities", {}) + parsed_entities = self._get_entities_from_metadata(entities_hier) + + if self._selection: + eager_schema = self.schema() + literal_names = ( + eager_schema.match(self._selection) + if isinstance(self._selection, str) and "*" in self._selection + else ( + [self._selection] if isinstance(self._selection, str) else list(self._selection) + ) + ) + deferred_select = list(dict.fromkeys(literal_names)) + + needed_long_internal = { + DataField(name).attribute if name not in CORE_COLUMNS else name + for name in deferred_select + } | set(CORE_COLUMNS) + physical_names = {f.name for f in self._catalog._tracking_table.schema().fields} + selected_long_fields = [ + DataField(f).to_iceberg_name() + for f in needed_long_internal + if DataField(f).to_iceberg_name() in physical_names + ] + + for req in [ + "match_id", + "entity_type", + "entity_id", + "frame_id", + "timestamp", + "period", + "x", + "y", + ]: + if req in physical_names and req not in selected_long_fields: + selected_long_fields.append(req) + + # 2. Perform the scan + scan_kwargs: dict[str, Any] = {"row_filter": filter_expr} + if selected_long_fields: + scan_kwargs["selected_fields"] = tuple(selected_long_fields) + if self._limit: + scan_kwargs["limit"] = self._limit * 50 + + arrow_table = self._catalog._tracking_table.scan(**scan_kwargs).to_arrow() + + # 3. Pivot using Rust core + mapping: dict[str, str] = {} + for entity, _ in parsed_entities: + eid = ( + str(entity.player_id) + if entity.is_player + else ( + f"referee:{entity.referee_id}" + if entity.is_referee + else (f"team:{entity.team_id}" if entity.is_team else "ball") + ) + ) + mapping[eid] = str(entity.team_id) if entity.team_id else "none" + + wide_container = TrackingDataContainer.pivot(arrow_table, entity_to_team=mapping) + + wide_container._catalog = self._catalog + + # 4. Inject constant attributes from metadata + from .._tracking_data_container import lit + + wide_names = wide_container.schema().names() + names_to_add = [] + values_to_add = [] + for entity, attrs in parsed_entities: + prefix = entity.cdf_prefix() + for attr, value in attrs.items(): + wide_name = f"{prefix}{attr}" + if wide_name not in wide_names: + names_to_add.append(wide_name) + values_to_add.append(lit(value)) + + if names_to_add: + wide_container = wide_container.add_columns(names_to_add, values_to_add) + + if hasattr(self, "_predicates") and self._predicates: + lazy_wide = wide_container.lazy() + for p in getattr(self, "_predicates"): + lazy_wide = lazy_wide.filter(p) + wide_container = lazy_wide.collect() + if deferred_select: + wide_container = wide_container.select(deferred_select) + if self._limit: + wide_container = wide_container.slice(0, self._limit) + + if "_schema_id" in metadata: + from ..schema import SchemaId + + schema_id = SchemaId.from_string(metadata["_schema_id"]) + wide_container._container = wide_container._container.with_schema_id(schema_id._inner) + + return wide_container + + def add_column( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> LazyBackend: + raise NotImplementedError("Iceberg backend does not support adding columns lazily yet.") + + def filter(self, predicate: _Expr) -> LazyBackend: + backend = IcebergBackend( + self._catalog, + self._match_id, + self._player_id, + selection=self._selection, + limit=self._limit, + **self._filters, + ) + backend._predicates = list(self._predicates) + [predicate] + return backend + + def transform_coordinates(self, sys: CoordinateSystem) -> LazyBackend: + raise NotImplementedError( + "Iceberg backend does not support coordinate transformation lazily yet." + ) + + def transform_orientation(self, mode: str | OrientationMode) -> LazyBackend: + raise NotImplementedError( + "Iceberg backend does not support orientation transformation lazily yet." + ) + + def __arrow_c_stream__(self, rs: Any = None) -> Any: + raise NotImplementedError("Iceberg backend does not support __arrow_c_stream__ directly.") diff --git a/python/tracking_data_container/builder.py b/python/tracking_data_container/builder.py new file mode 100644 index 0000000..5e3f43d --- /dev/null +++ b/python/tracking_data_container/builder.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from ._tracking_data_container import ( + _DataField, + _TrackingDataBuilder, +) +from .container import TrackingDataContainer +from .schema import DataField + + +class TrackingDataBuilder: + """ + A builder for efficiently constructing a TrackingDataContainer. + + The builder allows incremental insertion of data points across multiple + frames and columns, optimized for memory and performance. + + Examples + -------- + The builder is designed to support parallel data ingestion by allowing + multiple builders to be populated independently and then merged. + + >>> from concurrent.futures import ThreadPoolExecutor + >>> from tracking_data_container import TrackingDataBuilder + >>> + >>> def process_chunk(chunk_data): + ... builder = TrackingDataBuilder() + ... for frame_id, x, y in chunk_data: + ... builder.insert(frame_id, "ball/x", x) + ... builder.insert(frame_id, "ball/y", y) + ... return builder + >>> + >>> chunks = [ + ... [(0, 10.0, 20.0), (1, 11.0, 21.0)], + ... [(2, 12.0, 22.0), (3, 13.0, 23.0)] + ... ] + >>> + >>> with ThreadPoolExecutor() as executor: + ... builders = list(executor.map(process_chunk, chunks)) + >>> + >>> main_builder = TrackingDataBuilder() + >>> for b in builders: + ... main_builder.merge(b) + >>> + >>> container = main_builder.build() + """ + + _builder: _TrackingDataBuilder + + def __init__(self) -> None: + self._builder = _TrackingDataBuilder() + + def insert(self, frame_id: int, col: str | DataField, val: int | float | str | bool) -> None: + """ + Insert a value into the builder. + + Parameters + ---------- + frame_id : int + The frame index. + col : str or DataField + The column name or data field. + val : int, float, str, or bool + The value to insert. + """ + processed_col: str | _DataField + if isinstance(col, DataField): + processed_col = col._field + else: + processed_col = col + + if isinstance(val, bool): + self._builder.insert_bool(frame_id, processed_col, val) + elif isinstance(val, int): + self._builder.insert_int(frame_id, processed_col, val) + elif isinstance(val, float): + self._builder.insert_float(frame_id, processed_col, val) + elif isinstance(val, str): + self._builder.insert_string(frame_id, processed_col, val) + else: + raise TypeError(f"Unsupported value type: {type(val)}") + + def merge(self, other: TrackingDataBuilder) -> None: + """Merge another builder into this one.""" + self._builder.merge(other._builder) + + def build(self) -> TrackingDataContainer: + """Build the final TrackingDataContainer.""" + new_inner = self._builder.build() + return TrackingDataContainer(new_inner) diff --git a/python/tracking_data_container/catalogs/__init__.py b/python/tracking_data_container/catalogs/__init__.py new file mode 100644 index 0000000..44f786f --- /dev/null +++ b/python/tracking_data_container/catalogs/__init__.py @@ -0,0 +1,4 @@ +from .base import Catalog +from .iceberg import IcebergTrackingDataCatalog + +__all__ = ["Catalog", "IcebergTrackingDataCatalog"] diff --git a/python/tracking_data_container/catalogs/base.py b/python/tracking_data_container/catalogs/base.py new file mode 100644 index 0000000..1ab1e6b --- /dev/null +++ b/python/tracking_data_container/catalogs/base.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import logging +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from ..container import LazyTrackingDataContainer, TrackingDataContainer + +logger = logging.getLogger(__name__) + + +class Catalog(ABC): + """ + Abstract base class for a TrackingDataCatalog. + + A catalog manages the storage and lifecycle of TrackingDataContainer instances. + It acts as the repository for tracking data, allowing loading, saving, and + querying metadata. + """ + + @abstractmethod + def initialize(self) -> None: + """Initialize the catalog (e.g., create necessary tables/namespaces).""" + pass + + @abstractmethod + def load_container( + self, + match_id: str, + player_id: str | None = None, + **filters: Any, + ) -> LazyTrackingDataContainer: + """ + Load tracking data lazily from the catalog. + + Args: + match_id: The ID of the match to load. + player_id: Optional ID of a specific player to load. + **filters: Additional partition filters (e.g. frame_id ranges). + + Returns: + A lazy container pointing to the catalog data. + """ + pass + + @abstractmethod + def save_container( + self, + container: TrackingDataContainer, + match_id: str | None = None, + ) -> None: + """ + Save a tracking container to the catalog. + + Args: + container: The container to save. + match_id: Override match ID (extracted from container if None). + """ + pass + + @abstractmethod + def commit_container(self, container: TrackingDataContainer) -> None: + """ + Commit in-place modifications to an already-bound container. + + Args: + container: The bound container to commit. + """ + pass + + @abstractmethod + def get_metadata(self, match_id: str) -> dict[Any, Any]: + """ + Retrieve metadata associated with a match in the catalog. + + Args: + match_id: The ID of the match. + + Returns: + A dictionary containing the parsed match metadata. + """ + pass diff --git a/python/tracking_data_container/catalogs/iceberg.py b/python/tracking_data_container/catalogs/iceberg.py new file mode 100644 index 0000000..9ad25a6 --- /dev/null +++ b/python/tracking_data_container/catalogs/iceberg.py @@ -0,0 +1,441 @@ +"""TrackingDataCatalog - Manages container storage and lifecycle.""" + +from __future__ import annotations + +import json +import logging +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, cast + +import pyarrow as pa +from pyiceberg.catalog import load_catalog +from pyiceberg.exceptions import NoSuchTableError +from pyiceberg.expressions import ( + EqualTo, + Reference, +) +from pyiceberg.io.pyarrow import _pyarrow_to_schema_without_ids +from pyiceberg.partitioning import PartitionField, PartitionSpec +from pyiceberg.schema import Schema +from pyiceberg.table import Table +from pyiceberg.transforms import IdentityTransform +from pyiceberg.types import ( + FloatType, + LongType, + NestedField, + StringType, + TimestampType, +) + +from ..backends.iceberg import IcebergBackend +from ..container import LazyTrackingDataContainer, TrackingDataContainer +from .base import Catalog + + +@dataclass +class IcebergConfig: + """Configuration for Iceberg table optimization.""" + + # Partitioning + partition_by: list[str] = field(default_factory=lambda: ["match_id"]) + + # Parquet optimization + compression: str = "snappy" # or "zstd" + page_size: int = 1024 * 1024 # 1 MB + row_group_size: int = 128 * 1024 * 1024 # 128 MB + + # Storage features + write_page_index: bool = True + write_statistics: bool = True + use_dictionary: bool = True + + # Sorting + sort_by: list[str] = field(default_factory=lambda: ["entity_id", "timestamp"]) + + +logger = logging.getLogger(__name__) + + +class IcebergTrackingDataCatalog(Catalog): + """ + Manages storage and lifecycle of TrackingDataContainer instances. + + Backed by Apache Iceberg tables with configurable optimization settings. + Follows Iceberg's Table↔Catalog pattern where containers hold a + reference to their parent catalog. + """ + + def __init__( + self, + warehouse_path: str, + config: IcebergConfig | None = None, + catalog_type: str = "sql", + catalog_properties: dict | None = None, + ): + """ + Initialize catalog. + + Args: + warehouse_path: Path to Iceberg warehouse + config: Iceberg configuration (partitioning, compression, etc.) + catalog_type: Catalog backend (sql, glue, rest, etc.) + catalog_properties: Additional catalog properties + """ + self.warehouse_path = Path(warehouse_path).absolute() + self.config = config or IcebergConfig() + self.catalog_type = catalog_type + + # Create warehouse directory if it doesn't exist + self.warehouse_path.mkdir(parents=True, exist_ok=True) + + # Default properties for SQL catalog (SQLite for POC) + default_properties = { + "uri": f"sqlite:///{self.warehouse_path / 'catalog.db'}", + "warehouse": f"file://{self.warehouse_path}", + } + + self.catalog_properties = catalog_properties or default_properties + + # Load Iceberg catalog + self._iceberg_catalog = load_catalog( + "default", + **{ + "type": self.catalog_type, + **self.catalog_properties, + }, + ) + + self._tracking_table: Table | None = None + self._metadata_table: Table | None = None + + def initialize(self) -> None: + """ + Initialize Iceberg tables (tracking_data + match_metadata). + + Creates tables with configured partitioning and optimization settings. + Must be called once before first use. Idempotent - safe to call multiple times. + """ + from pyiceberg.exceptions import NamespaceAlreadyExistsError + + try: + self._iceberg_catalog.create_namespace("tracking") + except NamespaceAlreadyExistsError: + pass # Namespace already exists + + self._create_tracking_table() + self._create_metadata_table() + + def _create_tracking_table(self) -> None: + """Create tracking_data Iceberg table in the requested long format.""" + table_name = "tracking.tracking_data" + + # Check if table exists + try: + self._tracking_table = self._iceberg_catalog.load_table(table_name) + return # Table exists + except NoSuchTableError: + pass # Create new table + + # Define schema. + schema = Schema( + NestedField(1, "competition_id", StringType(), required=False), + NestedField(2, "season_id", StringType(), required=False), + NestedField(3, "match_id", StringType(), required=True), + NestedField(4, "frame_id", LongType(), required=True), + NestedField(5, "period", StringType(), required=True), + NestedField(6, "timestamp", TimestampType(), required=True), + NestedField(7, "entity_type", StringType(), required=True), + NestedField(8, "entity_id", StringType(), required=True), + NestedField(9, "x", FloatType(), required=True), + NestedField(10, "y", FloatType(), required=True), + NestedField(11, "z", FloatType(), required=False), + ) + + # Build PartitionSpec from config + partition_fields = [] + for field_name in self.config.partition_by: + field = schema.find_field(field_name) + partition_fields.append( + PartitionField( + source_id=field.field_id, + field_id=1000 + field.field_id, + transform=IdentityTransform(), + name=field_name, + ) + ) + partition_spec = PartitionSpec(*partition_fields) + + # Optimization properties + properties = { + "write.parquet.compression-codec": self.config.compression, + "write.parquet.page-size-bytes": str(self.config.page_size), + "write.parquet.row-group-size": str(self.config.row_group_size), + "write.parquet.dict-enabled": str(self.config.use_dictionary).lower(), + } + + self._tracking_table = self._iceberg_catalog.create_table( + table_name, + schema=schema, + partition_spec=partition_spec, + properties=properties, + ) + + def _create_metadata_table(self) -> None: + """Create match_metadata Iceberg table.""" + table_name = "tracking.match_metadata" + + # Check if table exists + try: + self._metadata_table = self._iceberg_catalog.load_table(table_name) + return + except NoSuchTableError: + pass + + # Define schema (simplified for POC) + schema = Schema( + NestedField(1, "match_id", StringType(), required=True), + NestedField(2, "competition_id", StringType(), required=False), + NestedField(3, "season_id", StringType(), required=False), + NestedField(4, "metadata_json", StringType(), required=True), + ) + + partition_spec = PartitionSpec( + PartitionField( + source_id=1, field_id=1001, transform=IdentityTransform(), name="match_id" + ) + ) + + self._metadata_table = self._iceberg_catalog.create_table( + table_name, + schema=schema, + partition_spec=partition_spec, + ) + + def load_container( + self, + match_id: str, + player_id: str | None = None, + **filters: Any, + ) -> LazyTrackingDataContainer: + """Load container from storage lazily.""" + backend = IcebergBackend(self, match_id=match_id, player_id=player_id, **filters) + return LazyTrackingDataContainer(backend) + + def save_container( + self, + container: TrackingDataContainer, + match_id: str | None = None, + metadata: dict | None = None, + ) -> None: + """Save container to storage (replaces match partition).""" + if match_id is None: + frame = container.materialize_frame(0) + try: + match_id = str(frame["match/id"]) + except KeyError: + match_id = str(frame["match_id"]) + + entities = self._save_tracking_data(container, match_id) + + final_metadata = metadata.copy() if metadata else {} + final_metadata["_entities"] = entities + if container.schema_id is not None: + final_metadata["_schema_id"] = str(container.schema_id) + self._save_metadata(final_metadata, match_id) + + def commit_container(self, container: TrackingDataContainer) -> None: + """Commit bound container.""" + frame = container.materialize_frame(0) + match_id = frame["match/id"] + + self.save_container(container, match_id=str(match_id)) + + def get_metadata(self, match_id: str) -> dict[Any, Any]: + """Load metadata for a match.""" + if self._metadata_table is None: + self._metadata_table = self._iceberg_catalog.load_table("tracking.match_metadata") + + scan = self._metadata_table.scan( + row_filter=EqualTo(term=Reference("match_id"), value=cast(Any, match_id)) # type: ignore + ) + arrow_table = scan.to_arrow() + + if len(arrow_table) == 0: + return {} + + metadata_json = arrow_table.column("metadata_json")[0].as_py() + return cast(dict[Any, Any], json.loads(metadata_json)) + + def _save_tracking_data( + self, + container: TrackingDataContainer, + match_id: str, + ) -> dict[str, Any]: + """Save tracking data to Iceberg table in entity-long format.""" + if self._tracking_table is None: + self._tracking_table = self._iceberg_catalog.load_table("tracking.tracking_data") + + # 1. Melt container natively in DataFusion + lazy_df = getattr(container._container.lazy(), "melt")() + + # 3. Constant Extraction (Normalization) + core_cols = { + "match/id", + "frame_id", + "period", + "timestamp", + "entity_type", + "entity_id", + "x", + "y", + "z", + "entity_key", + "player/id", + "team/id", + } + potential_constants = [] + for sch_field in container.schema(): + if sch_field.entity is not None: + attr = sch_field.attribute + if attr not in ("x", "y", "z") and attr not in core_cols: + potential_constants.append(attr) + potential_constants = list(set(potential_constants)) + + # Extract metadata via DataFusion natively + lazy_df, agg_table = lazy_df.extract_iceberg_metadata(match_id, potential_constants) + + # Build hierarchical meta + agg_dicts = agg_table.to_pylist() + constants_to_move = [] + for c in potential_constants: + # Check if all unique counts are exactly 1 or null (0) + if all(row.get(f"{c}_count") in (1, 0, None) for row in agg_dicts): + constants_to_move.append(c) + + hierarchical_meta: dict[str, Any] = {"ball": {}, "teams": {}, "referees": {}} + for row in agg_dicts: + etype = row["entity_type"] + eid = row["entity_id"] + tid = row.get("team/id") or "none" + + attrs = {} + for c in constants_to_move: + val = row.get(f"{c}_val") + if val is not None: + attrs[c] = val + + if not attrs: + continue + + if etype == "ball": + hierarchical_meta["ball"].update(attrs) + elif etype == "referee": + hierarchical_meta["referees"].setdefault(eid, {}).update(attrs) + elif etype == "player": + team = hierarchical_meta["teams"].setdefault(tid, {"players": {}}) + team["players"].setdefault(eid, {}).update(attrs) + elif etype == "team": + target_tid = tid if tid != "none" else eid.replace("team:", "") + team = hierarchical_meta["teams"].setdefault(target_tid, {"players": {}}) + team.update(attrs) + + # Drop constant columns AND the team/id column from physical table via DataFusion + cols_to_drop = constants_to_move + ["team/id"] + lazy_df = lazy_df.drop_columns(cols_to_drop) + + # Convert to Arrow natively + arrow_table = pa.table(lazy_df) + + # Validation + col_names = arrow_table.column_names + if "entity_key" not in col_names: + raise ValueError("entity_key column is required for saving to catalog") + + # 4. Final Validation & Save + for req_field in ["x", "y"]: + if req_field not in col_names: + raise ValueError(f"Required column {req_field} is missing") + + must_not_be_null = [ + "match_id", + "frame_id", + "period", + "timestamp", + "entity_type", + "entity_id", + ] + for req_field in must_not_be_null: + if req_field not in col_names: + raise ValueError(f"Required field {req_field} is missing from container") + if arrow_table.column(req_field).null_count > 0: + raise ValueError(f"Required field {req_field} contains null values") + + # Convert to Arrow types + known_types = { + "timestamp": pa.timestamp("us"), + "frame_id": pa.int64(), + "x": pa.float32(), + "y": pa.float32(), + "z": pa.float32(), + } + + new_cols, new_fields = [], [] + for i, pa_field in enumerate(arrow_table.schema): + target = known_types.get(pa_field.name) or ( + pa_field.type.value_type + if isinstance(pa_field.type, pa.DictionaryType) + else (pa.string() if pa.types.is_null(pa_field.type) else None) + ) + new_cols.append(arrow_table.column(i).cast(target) if target else arrow_table.column(i)) + new_fields.append( + pa.field(pa_field.name, target, nullable=True) + if target + else pa.field(pa_field.name, pa_field.type, nullable=True) + ) + + arrow_table = pa.Table.from_arrays(new_cols, schema=pa.schema(new_fields)) + + with self._tracking_table.transaction() as txn: + txn.update_schema().union_by_name( + _pyarrow_to_schema_without_ids(arrow_table.schema) + ).commit() + txn.delete(EqualTo(term=Reference("match_id"), value=cast(Any, match_id))) # type: ignore + txn.append(arrow_table) + + return hierarchical_meta + + def _save_metadata(self, metadata: dict, match_id: str) -> None: + """Save metadata to Iceberg table.""" + if self._metadata_table is None: + self._metadata_table = self._iceberg_catalog.load_table("tracking.match_metadata") + + metadata_schema = pa.schema( + [ + pa.field("match_id", pa.string(), nullable=False), + pa.field("competition_id", pa.string(), nullable=True), + pa.field("season_id", pa.string(), nullable=True), + pa.field("metadata_json", pa.string(), nullable=False), + ] + ) + + metadata_row = pa.table( + { + "match_id": [match_id], + "competition_id": [metadata.get("competition_id")], + "season_id": [metadata.get("season_id")], + "metadata_json": [json.dumps(metadata)], + }, + schema=metadata_schema, + ) + + with self._metadata_table.transaction() as txn: + txn.delete(EqualTo(term=Reference("match_id"), value=cast(Any, match_id))) # type: ignore + txn.append(metadata_row) + + def __repr__(self) -> str: + return ( + f"IcebergTrackingDataCatalog(" + f"warehouse='{self.warehouse_path}', " + f"type='{self.catalog_type}')" + ) diff --git a/python/tracking_data_container/container.py b/python/tracking_data_container/container.py new file mode 100644 index 0000000..350494f --- /dev/null +++ b/python/tracking_data_container/container.py @@ -0,0 +1,638 @@ +from __future__ import annotations + +import logging +from collections.abc import Sequence +from datetime import datetime, timedelta +from typing import TYPE_CHECKING, Any, cast + +import numpy as np +import polars as pl +import pyarrow as pa + +from ._tracking_data_container import ( + CORE_COLUMNS as _CORE_COLUMNS, +) +from ._tracking_data_container import ( + Expr as _Expr, +) +from ._tracking_data_container import ( + Frame, + OrientationMode, + _DataField, + _TrackingDataContainer, +) +from ._tracking_data_container import ( + Selection as _Selection, +) +from .coordinate_system import CoordinateSystem +from .schema import DataField, Fields, SchemaId +from .selector import ColumnSelector, Selection + +if TYPE_CHECKING: + from pathlib import Path + + import pandas as pd + + from .backends.base import LazyBackend + from .catalogs.base import Catalog + +logger = logging.getLogger(__name__) + +# --- Constants for performance --- + +CORE_COLUMNS = set(_CORE_COLUMNS) + + +class TrackingDataContainer: + """A high-performance, metadata-aware container for sports tracking data.""" + + _container: _TrackingDataContainer + _catalog: Catalog | None + + def __init__( + self, + data: dict[str, Any] | _TrackingDataContainer | pl.DataFrame | pa.Table, + coordinate_system: CoordinateSystem | None = None, + orientation: str | OrientationMode | None = None, + catalog: Catalog | None = None, + validate: bool = True, + schema: str | SchemaId | None = None, + ) -> None: + self._catalog = catalog + if isinstance(data, _TrackingDataContainer): + self._container = data + if schema is not None: + if isinstance(schema, str): + schema = SchemaId.from_string(schema) + self._container = self._container.with_schema_id(schema._inner) + return + + data_dict: dict[str, Any] + if isinstance(data, pl.DataFrame): + data_dict = data.to_dict(as_series=False) + elif isinstance(data, pa.Table): + data_dict = data.to_pydict() + else: + data_dict = data + + inner_cs = coordinate_system._inner if coordinate_system else None + self._container = _TrackingDataContainer( + data_dict, inner_cs, str(orientation) if orientation else None + ) + if schema is not None: + if isinstance(schema, str): + schema = SchemaId.from_string(schema) + self._container = self._container.with_schema_id(schema._inner) + if validate: + self.validate() + + def num_rows(self) -> int: + return self._container.num_rows() + + def schema(self) -> Fields: + return Fields(self._container.schema) + + @property + def schema_id(self) -> SchemaId | None: + """Returns the schema identifier, if set.""" + inner = self._container.schema_id + if inner is None: + return None + return SchemaId._from_inner(inner) + + def validate(self) -> None: + """Validate the container against its schema. + + If a ``schema_id`` is set, validates against that schema. + Otherwise, validates against the default schema (CDF v0.2.3 tracking). + """ + if self._container.schema_id is not None: + self._container.validate_with_schema(self._container.schema_id) + else: + self._container.validate() + + def is_bound(self) -> bool: + return self._catalog is not None + + def save(self) -> None: + if self._catalog is None: + raise ValueError("Container is not bound to a catalog.") + self._catalog.save_container(self) + + @property + def selector(self) -> ColumnSelector: + return ColumnSelector(self._container.selector) + + @property + def coordinate_system(self) -> CoordinateSystem | None: + if self._container.coordinate_system is None: + return None + return CoordinateSystem._from_inner(self._container.coordinate_system) + + @property + def orientation_mode(self) -> OrientationMode | None: + return self._container.orientation_mode + + def with_coordinate_system(self, system: CoordinateSystem) -> TrackingDataContainer: + new_inner = self._container.with_coordinate_system(system._inner) + return TrackingDataContainer(new_inner, catalog=self._catalog) + + def transform_coordinates(self, to_system: CoordinateSystem) -> TrackingDataContainer: + new_inner = self._container.transform_coordinates(to_system._inner) + return TrackingDataContainer(new_inner, catalog=self._catalog) + + def with_orientation(self, mode: str | OrientationMode) -> TrackingDataContainer: + new_inner = self._container.with_orientation(str(mode)) + return TrackingDataContainer(new_inner, catalog=self._catalog) + + def transform_orientation(self, mode: str | OrientationMode) -> TrackingDataContainer: + new_inner = self._container.transform_orientation(str(mode)) + return TrackingDataContainer(new_inner, catalog=self._catalog) + + def select(self, items: str | Sequence[str | DataField | Selection]) -> TrackingDataContainer: + literal_names: list[str] = [] + input_items = items if isinstance(items, (list, tuple)) else [items] + for item in input_items: + if isinstance(item, str): + if "*" in item: + literal_names.extend(self.schema().match(item)) + else: + literal_names.append(item) + elif isinstance(item, DataField): + literal_names.append(item.name()) + + processed: list[str | _DataField | _Selection] = [] + if literal_names: + processed = [_DataField.from_name(n) for n in literal_names] + else: + for i in input_items: + if isinstance(i, DataField): + processed.append(i._field) + elif isinstance(i, Selection): + processed.append(i._inner) + elif isinstance(i, str): + processed.append(_DataField.from_name(i)) + elif isinstance(i, (_DataField, _Selection)): + processed.append(i) + + new_inner = self._container.select(processed) + return TrackingDataContainer(new_inner, catalog=self._catalog, validate=False) + + def slice(self, offset: int, length: int) -> TrackingDataContainer: + new_inner = self._container.slice(offset, length) + return TrackingDataContainer(new_inner, catalog=self._catalog, validate=False) + + def add_column( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> TrackingDataContainer: + if callable(value): + if on is not None: + on_resolved = [on] if isinstance(on, str) else list(on) + try: + result = value(*on_resolved) + if isinstance(result, _Expr): + new_inner = self._container.add_column(name, result) + return TrackingDataContainer( + new_inner, catalog=self._catalog, validate=False + ) + except Exception: + pass + + inputs = self.select(on) if on is not None else self + batch = inputs.to_arrow() + args = [batch.column(c) for c in ([on] if isinstance(on, str) else list(on or []))] + data = value(*args) if on else value(batch) + elif isinstance(value, _Expr): + new_inner = self._container.add_column(name, value) + return TrackingDataContainer(new_inner, catalog=self._catalog, validate=False) + else: + data = value + + try: + cap_arr, cap_sch = data.__arrow_c_array__() + except (AttributeError, TypeError): + import pyarrow as pa + + data = pa.array(data) + cap_arr, cap_sch = data.__arrow_c_array__() + return TrackingDataContainer( + self._container.add_column(name, cap_arr, cap_sch), + catalog=self._catalog, + validate=False, + ) + + def add_columns(self, names: Sequence[str], values: Sequence[Any]) -> TrackingDataContainer: + expr_names = [] + expr_values = [] + array_names = [] + array_values = [] + schema_capsules = [] + + for name, value in zip(names, values): + if isinstance(value, _Expr): + expr_names.append(name) + expr_values.append(value) + elif callable(value): + # Fallback to single add_column for callables + return self.add_column(name, value) + else: + data = value + try: + cap_arr, cap_sch = data.__arrow_c_array__() + except (AttributeError, TypeError): + import pyarrow as pa + + data = pa.array(data) + cap_arr, cap_sch = data.__arrow_c_array__() + + array_names.append(name) + array_values.append(cap_arr) + schema_capsules.append(cap_sch) + + if hasattr(self._container, "clone"): + new_inner = self._container.clone() + else: + new_inner = self._container + + if expr_names or array_names: + # We call the Rust method, passing lists of Exprs or capsules. + # We must map expr_names, expr_values properly if we had a split API, + # but _TrackingDataContainer.add_columns accepts a mixed list of values! + pass + + # Since _TrackingDataContainer.add_columns takes a mixed list, let's just pass them directly + # and extract the capsules for array values. + + capsules: list[Any] = [] + final_values = [] + for v in values: + if isinstance(v, _Expr): + final_values.append(v) + capsules.append(None) + else: + try: + cap_arr, cap_sch = v.__arrow_c_array__() + except (AttributeError, TypeError): + import pyarrow as pa + + v = pa.array(v) + cap_arr, cap_sch = v.__arrow_c_array__() + final_values.append(cap_arr) + capsules.append(cap_sch) + + new_inner = self._container.add_columns( + list(names), final_values, _schema_capsules=capsules + ) + return TrackingDataContainer(new_inner, catalog=self._catalog, validate=False) + + def add_column_per_player( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> TrackingDataContainer: + names, values_list, ons = [], [], [] + for player in self.schema().players(): + col_name = DataField(entity=player, attribute=name).name() + names.append(col_name) + player_on: str | list[str] | None = None + if on is not None: + if isinstance(on, str): + player_on = DataField(entity=player, attribute=on).name() + elif isinstance(on, (list, tuple)): + player_on = [DataField(entity=player, attribute=attr).name() for attr in on] + ons.append(player_on) + values_list.append(value) + res = self + for n, v, o in zip(names, values_list, ons): + res = res.add_column(n, v, on=o) + return res + + def to_arrow(self) -> pa.RecordBatch: + return pa.record_batch(self._container.to_arrow()) + + def to_polars(self) -> pl.DataFrame: + df = pl.from_arrow(self.to_arrow()) + if isinstance(df, pl.Series): + df = df.to_frame() + return df + + def to_pandas(self) -> pd.DataFrame: + import pandas as pd + + return cast(pd.DataFrame, self.to_arrow().to_pandas()) + + def to_parquet(self, path: str | Path) -> None: + self._container.save_parquet(str(path)) + + def __arrow_c_schema__(self) -> Any: + return self._container.__arrow_c_schema__() + + def __arrow_c_array__(self, requested_schema: Any = None) -> Any: + return self._container.__arrow_c_array__(requested_schema) + + @classmethod + def from_arrow( + cls, + data: pa.Table | pa.RecordBatch | pa.StructArray | pa.ChunkedArray, + catalog: Catalog | None = None, + ) -> TrackingDataContainer: + import pyarrow as pa + + if isinstance(data, pa.Table): + data = data.combine_chunks() + batch = data.to_batches()[0] + data = pa.StructArray.from_arrays(batch.columns, names=batch.schema.names) + elif isinstance(data, pa.RecordBatch): + data = pa.StructArray.from_arrays(data.columns, names=data.schema.names) + elif hasattr(data, "combine_chunks"): + data = data.combine_chunks() + + assert isinstance(data, pa.StructArray) + cap_arr, cap_sch = data.__arrow_c_array__() + new_inner = _TrackingDataContainer.from_arrow(cap_arr, cap_sch, None, None) + return TrackingDataContainer(new_inner, catalog=catalog, validate=False) + + def melt(self) -> pl.DataFrame: + """Pivots wide-format tracking data into entity-long format. + + Returns: + A long Polars DataFrame. + """ + import pyarrow as pa + + melted_stream = getattr(self._container.lazy(), "melt")() + table = pa.table(melted_stream) + return cast(pl.DataFrame, pl.from_arrow(table)) + + @classmethod + def pivot( + cls, + df: pl.DataFrame | pa.Table | pa.StructArray | pa.RecordBatch, + entity_to_team: dict[str, str] | None = None, + ) -> TrackingDataContainer: + """Pivots long-format tracking data into wide-format using Rust implementation.""" + if hasattr(df, "to_arrow"): + df = df.to_arrow() + + import pyarrow as pa + + if isinstance(df, pa.Table): + df = df.combine_chunks() + # RecordBatch.from_arrays expects Arrays, not ChunkedArrays + df = pa.RecordBatch.from_arrays([c.chunk(0) for c in df.columns], names=df.schema.names) + elif isinstance(df, pa.StructArray): + # Convert StructArray to RecordBatch by creating a table first + df = pa.RecordBatch.from_arrays(df.flatten(), names=[f.name for f in df.type]) + + # Use the underlying Rust implementation for performance + inner = _TrackingDataContainer.pivot(df, entity_to_team) + return TrackingDataContainer(inner, validate=False) + + def lazy(self) -> LazyTrackingDataContainer: + from .backends.datafusion import DataFusionBackend + + return LazyTrackingDataContainer(DataFusionBackend(self._container.lazy())) + + def materialize_frame(self, frame: int | datetime | timedelta) -> Frame: + """Materializes a single frame. Fixed datetime comparison.""" + if isinstance(frame, int): + if frame < 0 or frame >= self.num_rows(): + raise IndexError("Frame out of range.") + return self._container.materialize_frame(frame) + + df = self.select(["timestamp", "frame_id"]).to_polars() + if len(df) == 0: + raise ValueError("Container is empty.") + + if isinstance(frame, datetime): + target = int(frame.timestamp() * 1_000_000) + ts_values = df["timestamp"].cast(pl.Int64).to_numpy() + matches = np.where(ts_values == target)[0] + if len(matches) == 0: + raise ValueError(f"No frame found at {frame}") + idx = int(matches[0]) + elif isinstance(frame, timedelta): + if "frame_id" in df.columns: + target_fid = df["frame_id"][0] + int(frame.total_seconds() * 25) + matches = np.where(df["frame_id"].to_numpy() == target_fid)[0] + if len(matches) == 0: + raise ValueError(f"No frame found at offset {frame}") + idx = int(matches[0]) + else: + target_ts = df["timestamp"][0] + int(frame.total_seconds() * 1_000_000) + matches = np.where(df["timestamp"].cast(pl.Int64).to_numpy() >= target_ts)[0] + if len(matches) == 0: + raise ValueError(f"No frame found at offset {frame}") + idx = int(matches[0]) + else: + raise TypeError(f"Unsupported frame type: {type(frame)}") + + return self._container.materialize_frame(idx) + + def plot_frame( + self, + frame: int | datetime | timedelta, + ax: Any = None, + home_color: str = "red", + away_color: str = "blue", + ball_color: str = "white", + referee_color: str = "yellow", + player_size: float = 80, + ball_size: float = 50, + **kwargs: Any, + ) -> Any: + """ + Visualizes a frame using mplsoccer. + + Parameters + ---------- + frame : int, datetime, or timedelta + The frame index, timestamp, or timedelta to plot. + ax : matplotlib.axes.Axes, optional + An existing matplotlib Axes object to plot on. + home_color : str, default="red" + Color for the home team players. + away_color : str, default="blue" + Color for the away team players. + ball_color : str, default="white" + Color for the ball. + referee_color : str, default="yellow" + Color for the referees. + player_size : float, default=0.8 + Radius of the players in pitch units. + ball_size : float, default=0.5 + Radius of the ball in pitch units. + **kwargs + Additional arguments passed to the Pitch.scatter method. + + Returns + ------- + matplotlib.axes.Axes + The matplotlib Axes object. + + Raises + ------ + ImportError + If mplsoccer is not installed. + ValueError + If the coordinate system is not defined. + """ + try: + from mplsoccer import Pitch + except ImportError: + raise ImportError( + "Seems like you don't have `mplsoccer` installed. " + "Please install it using: pip install mplsoccer" + ) + + if self.coordinate_system is None: + raise ValueError( + "Coordinate system must be defined to plot. " + "Use container.with_coordinate_system() to set it." + ) + + dot_styles = { + "home": { + "facecolor": home_color, + "edgecolor": "white", + "linewidth": 0.8, + "s": player_size, + "zorder": 5, + }, + "away": { + "facecolor": away_color, + "edgecolor": "white", + "linewidth": 0.8, + "s": player_size, + "zorder": 5, + }, + "ball": { + "facecolor": ball_color, + "edgecolor": "black", + "s": ball_size, + "zorder": 6, + }, + "referee": { + "facecolor": referee_color, + "edgecolor": "black", + "linewidth": 0.8, + "s": player_size, + "zorder": 5, + }, + } + + dims = self.coordinate_system.to_mplsoccer() + pitch = Pitch(pitch_type=dims) + + if ax is None: + fig, ax = pitch.draw() + else: + pitch.draw(ax=ax) + + f = self.materialize_frame(frame) + schema = self.schema() + + # Plot players + for tracked_team in schema.teams(): + from .schema import Team + + if not isinstance(tracked_team, Team): + continue + team_id = tracked_team.team_id + if team_id is None: + continue + style_key = "home" if "home" in team_id.lower() else "away" + style = dot_styles[style_key].copy() + style.update(kwargs) + + players = schema.players(team=team_id) + x = [] + y = [] + for p in players: + px = f[DataField(entity=p, attribute="x")] # type: ignore + py = f[DataField(entity=p, attribute="y")] # type: ignore + if px is not None and py is not None: + x.append(px) + y.append(py) + + if x: + pitch.scatter(x, y, ax=ax, label=f"Team {team_id}", **style) + + # Plot ball + from .schema import Ball + + bx = f[DataField(entity=Ball(), attribute="x")] # type: ignore + by = f[DataField(entity=Ball(), attribute="y")] # type: ignore + if bx is not None and by is not None: + style = dot_styles["ball"].copy() + style.update(kwargs) + pitch.scatter(bx, by, ax=ax, label="Ball", **style) + + # Plot referees + rx = [] + ry = [] + for ref in schema.referees(): + px = f[DataField(entity=ref, attribute="x")] # type: ignore + py = f[DataField(entity=ref, attribute="y")] # type: ignore + if px is not None and py is not None: + rx.append(px) + ry.append(py) + + if rx: + style = dot_styles["referee"].copy() + style.update(kwargs) + pitch.scatter(rx, ry, ax=ax, label="Referees", **style) + + return ax + + @classmethod + def from_parquet(cls, path: str | Path) -> TrackingDataContainer: + """Loads from a Parquet file.""" + new_inner = _TrackingDataContainer.load_parquet(str(path)) + return TrackingDataContainer(new_inner, validate=False) + + def __repr__(self) -> str: + return repr(self._container) + + +class LazyTrackingDataContainer: + """A lazy tracking data container that delegates execution to a backend.""" + + def __init__(self, backend: LazyBackend): + self._backend = backend + + def schema(self) -> Fields: + return self._backend.schema() + + @property + def selector(self) -> ColumnSelector: + from ._tracking_data_container import ColumnSelector as _ColumnSelector + + return ColumnSelector(_ColumnSelector()) + + def select(self, items: str | Sequence[str]) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.select(items)) + + def add_column( + self, name: str, value: Any, on: str | Sequence[str] | None = None + ) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.add_column(name, value, on)) + + def collect(self) -> TrackingDataContainer: + return self._backend.collect() + + def slice(self, offset: int, length: int) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.slice(offset, length)) + + def filter(self, predicate: _Expr) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.filter(predicate)) + + def transform_coordinates(self, sys: CoordinateSystem) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.transform_coordinates(sys)) + + def transform_orientation(self, mode: str | OrientationMode) -> LazyTrackingDataContainer: + return LazyTrackingDataContainer(self._backend.transform_orientation(mode)) + + def __arrow_c_stream__(self, rs: Any = None) -> Any: + return self._backend.__arrow_c_stream__(rs) + + def __repr__(self) -> str: + return f"LazyTrackingDataContainer(backend={self._backend.__class__.__name__})" diff --git a/python/tracking_data_container/coordinate_system.py b/python/tracking_data_container/coordinate_system.py new file mode 100644 index 0000000..6ea6d3a --- /dev/null +++ b/python/tracking_data_container/coordinate_system.py @@ -0,0 +1,233 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._tracking_data_container import ( + Direction, + LengthUnit, + Origin, + _CoordinateSystem, +) + +if TYPE_CHECKING: + from mplsoccer.soccer.dimensions import BaseSoccerDims + + +class CoordinateSystem: + """ + Defines a coordinate system for tracking data. + + A coordinate system combines spatial bounds (ranges), an origin point, axis + orientations, and measurement units to provide a complete context for entity + positions. + + Parameters + ---------- + x_range : tuple[float, float], optional + The valid range of X coordinates (e.g., `(-52.5, 52.5)`). + y_range : tuple[float, float], optional + The valid range of Y coordinates (e.g., `(-34.0, 34.0)`). + origin : Origin or str, default="center" + The location of the (0,0) point. Can be "center", "top_left", or "bottom_left". + x_direction : Direction or str, default="right" + The direction in which X values increase. Can be "right" or "left". + y_direction : Direction or str, default="up" + The direction in which Y values increase. Can be "up" or "down". + length_unit : LengthUnit or str, optional + The physical unit of length for the coordinates. Can be "meters", + "centimeters", "millimeters", "yards", "inches", "feet", or "percent". + + Examples + -------- + Defining a standard metric coordinate system: + + >>> from tracking_data_container import CoordinateSystem + >>> metric_sys = CoordinateSystem( + ... x_range=(-52.5, 52.5), + ... y_range=(-34.0, 34.0), + ... origin="center", + ... length_unit="meters" + ... ) + + Defining a normalized coordinate system (0 to 1): + + >>> norm_sys = CoordinateSystem( + ... x_range=(0.0, 1.0), + ... y_range=(0.0, 1.0), + ... origin="bottom_left", + ... length_unit="percent" + ... ) + """ + + _inner: _CoordinateSystem + + def __init__( + self, + x_range: tuple[float, float] | None = None, + y_range: tuple[float, float] | None = None, + origin: Origin | str = Origin.Center, + x_direction: Direction | str = Direction.Right, + y_direction: Direction | str = Direction.Up, + length_unit: LengthUnit | str | None = None, + ) -> None: + self._inner = _CoordinateSystem( + x_range=x_range, + y_range=y_range, + origin=origin, + x_direction=x_direction, + y_direction=y_direction, + length_unit=length_unit, + ) + + @staticmethod + def _from_inner(inner: _CoordinateSystem) -> CoordinateSystem: + """Internal helper to create a CoordinateSystem from the Rust representation.""" + instance = CoordinateSystem.__new__(CoordinateSystem) + instance._inner = inner + return instance + + @property + def x_range(self) -> tuple[float, float] | None: + """The valid range of X coordinates.""" + return self._inner.x_range + + @property + def y_range(self) -> tuple[float, float] | None: + """The valid range of Y coordinates.""" + return self._inner.y_range + + @property + def origin(self) -> str: + """The location of the (0,0) point.""" + return self._inner.origin + + @property + def x_direction(self) -> str: + """The direction in which X values increase.""" + return self._inner.x_direction + + @property + def y_direction(self) -> str: + """The direction in which Y values increase.""" + return self._inner.y_direction + + @property + def length_unit(self) -> str | None: + """The physical unit of length for the coordinates.""" + return self._inner.length_unit + + def __repr__(self) -> str: + return repr(self._inner) + + def to_mplsoccer(self) -> BaseSoccerDims: + """ + Convert the coordinate system to a mplsoccer BaseSoccerDims object. + + This allows using the coordinate system directly with mplsoccer's Pitch class. + + Returns + ------- + mplsoccer.soccer.dimensions.BaseSoccerDims + The dimensions object for mplsoccer. + + Examples + -------- + >>> from tracking_data_container import CoordinateSystem + >>> from mplsoccer import Pitch + >>> coordinate_system = CoordinateSystem(x_range=(-52.5, 52.5), y_range=(-34, 34)) + >>> pitch = Pitch(pitch_type=coordinate_system.to_mplsoccer()) + + Notes + ----- + This method is experimental and requires the `mplsoccer` package. + """ + try: + from mplsoccer.soccer.dimensions import BaseSoccerDims + except ImportError: + raise ImportError( + "Seems like you don't have `mplsoccer` installed. " + "Please install it using: pip install mplsoccer" + ) + + if self.x_range is None or self.y_range is None: + raise ValueError("The x and y dimensions of the pitch must be fully defined.") + + x_min, x_max = self.x_range + y_min, y_max = self.y_range + + width = x_max - x_min + length = y_max - y_min + + # Estimate common football pitch dimensions if not specified by units + pitch_length = width if self.length_unit != "percent" else 105.0 + pitch_width = length if self.length_unit != "percent" else 68.0 + + invert_y = self.y_direction == "down" + origin_center = self.origin == "center" + + # Goal and area dimensions in meters (standard) + goal_width = 7.32 + six_yard_width = 18.32 + six_yard_length = 5.5 + penalty_area_width = 40.32 + penalty_area_length = 16.5 + penalty_spot_distance = 11.0 + circle_radius = 9.15 + corner_radius = 1.0 + + # Scale if using percent + if self.length_unit == "percent": + scale = width + goal_width = goal_width / 68.0 * scale + six_yard_width = six_yard_width / 68.0 * scale + six_yard_length = six_yard_length / 105.0 * scale + penalty_area_width = penalty_area_width / 68.0 * scale + penalty_area_length = penalty_area_length / 105.0 * scale + penalty_spot_distance = penalty_spot_distance / 105.0 * scale + circle_radius = circle_radius / 105.0 * scale + corner_radius = corner_radius / 105.0 * scale + + center_x = (x_min + x_max) / 2 + center_y = (y_min + y_max) / 2 + + dims = BaseSoccerDims( + left=x_min, + right=x_max, + bottom=y_min if not invert_y else y_max, + top=y_max if not invert_y else y_min, + width=width, + length=length, + goal_bottom=center_y - goal_width / 2, + goal_top=center_y + goal_width / 2, + six_yard_left=x_min + six_yard_length, + six_yard_right=x_max - six_yard_length, + six_yard_bottom=center_y - six_yard_width / 2, + six_yard_top=center_y + six_yard_width / 2, + penalty_spot_distance=penalty_spot_distance, + penalty_left=x_min + penalty_spot_distance, + penalty_right=x_max - penalty_spot_distance, + penalty_area_left=x_min + penalty_area_length, + penalty_area_right=x_max - penalty_area_length, + penalty_area_bottom=center_y - penalty_area_width / 2, + penalty_area_top=center_y + penalty_area_width / 2, + center_width=center_y, + center_length=center_x, + goal_width=goal_width, + goal_length=0, + six_yard_width=six_yard_width, + six_yard_length=six_yard_length, + penalty_area_width=penalty_area_width, + penalty_area_length=penalty_area_length, + circle_diameter=circle_radius * 2, + corner_diameter=corner_radius * 2, + invert_y=invert_y, + origin_center=origin_center, + pad_default=0.02 * width, + pad_multiplier=1, + aspect_equal=self.length_unit != "percent", + pitch_width=pitch_width, + pitch_length=pitch_length, + aspect=pitch_width / pitch_length if self.length_unit == "percent" else 1.0, + ) + dims.setup_dims() + return dims diff --git a/python/tracking_data_container/exceptions.py b/python/tracking_data_container/exceptions.py new file mode 100644 index 0000000..8a9a219 --- /dev/null +++ b/python/tracking_data_container/exceptions.py @@ -0,0 +1,13 @@ +from ._tracking_data_container import ( + InvalidParametersError, + SchemaError, + SchemaWarning, + TrackingDataContainerError, +) + +__all__ = ( + "TrackingDataContainerError", + "InvalidParametersError", + "SchemaError", + "SchemaWarning", +) diff --git a/python/tracking_data_container/io.py b/python/tracking_data_container/io.py new file mode 100644 index 0000000..7391a3f --- /dev/null +++ b/python/tracking_data_container/io.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from ._tracking_data_container import ( + _TrackingDataContainer, +) +from ._tracking_data_container import ( + load_hawkeye as _load_hawkeye, +) +from ._tracking_data_container import ( + load_sportec as _load_sportec, +) +from ._tracking_data_container import ( + load_statsperform as _load_statsperform, +) +from ._tracking_data_container import ( + scan_parquet as _scan_parquet, +) +from .container import LazyTrackingDataContainer, TrackingDataContainer + +if TYPE_CHECKING: + pass + + +def scan_parquet(path: str | Path) -> LazyTrackingDataContainer: + """ + Lazily scans a Parquet file. + + Parameters + ---------- + path : str or Path + The path to the Parquet file. + + Returns + ------- + LazyTrackingDataContainer + A lazy container representing the Parquet file. + """ + from .backends.datafusion import DataFusionBackend + + new_inner = _scan_parquet(str(path)) + return LazyTrackingDataContainer(DataFusionBackend(new_inner)) + + +def load_sportec(path: str | Path, meta_path: str | Path | None = None) -> TrackingDataContainer: + """ + Loads a Sportec XML file into an eager container. + + Parameters + ---------- + path : str or Path + The path to the Sportec tracking data XML file. + meta_path : str or Path, optional + The path to the Sportec metadata XML file. + + Returns + ------- + TrackingDataContainer + An eager container containing the loaded data. + """ + new_inner = _load_sportec(str(path), str(meta_path) if meta_path else None) + return TrackingDataContainer(_TrackingDataContainer._from_capsule(new_inner)) + + +def load_statsperform(path: str | Path) -> TrackingDataContainer: + """ + Loads a StatsPerform tracking file into an eager container. + + Parameters + ---------- + path : str or Path + The path to the StatsPerform data file. + + Returns + ------- + TrackingDataContainer + An eager container containing the loaded data. + """ + new_inner = _load_statsperform(str(path)) + return TrackingDataContainer(_TrackingDataContainer._from_capsule(new_inner)) + + +def load_hawkeye( + ball_paths: list[str | Path], + centroid_paths: list[str | Path], + joint_paths: list[str | Path] | None = None, + meta_path: str | Path | None = None, +) -> TrackingDataContainer: + """ + Loads Hawk-Eye tracking files into an eager container. + + Parameters + ---------- + ball_paths : list of str or Path + The paths to the Hawk-Eye ball tracking data files. + centroid_paths : list of str or Path + The paths to the Hawk-Eye player centroid data files. + joint_paths : list of str or Path, optional + The paths to the Hawk-Eye player joint data files. + meta_path : str or Path, optional + The path to the Hawk-Eye metadata JSON file. + + Returns + ------- + TrackingDataContainer + An eager container containing the loaded data. + """ + new_inner = _load_hawkeye( + [str(p) for p in ball_paths], + [str(p) for p in centroid_paths], + [str(p) for p in joint_paths] if joint_paths else None, + str(meta_path) if meta_path else None, + ) + return TrackingDataContainer(_TrackingDataContainer._from_capsule(new_inner)) diff --git a/python/tracking_data_container/py.typed b/python/tracking_data_container/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/python/tracking_data_container/schema.py b/python/tracking_data_container/schema.py new file mode 100644 index 0000000..19855fc --- /dev/null +++ b/python/tracking_data_container/schema.py @@ -0,0 +1,524 @@ +from __future__ import annotations + +from collections.abc import Iterator +from typing import cast + +from ._tracking_data_container import ( + _DataField, + _Fields, + _SchemaId, + _TrackedEntity, +) +from ._tracking_data_container import ( + list_schemas as _list_schemas, +) + + +class TrackedEntity: + """Represents an entity being tracked (ball, player, team, etc.).""" + + def __init__(self, entity: _TrackedEntity) -> None: + self._entity = entity + + @property + def is_ball(self) -> bool: + return self._entity.is_ball() + + @property + def is_player(self) -> bool: + return self._entity.is_player() + + @property + def is_team(self) -> bool: + return self._entity.is_team() + + @property + def is_referee(self) -> bool: + return self._entity.is_referee() + + @property + def type(self) -> str: + if self.is_ball: + return "ball" + if self.is_player: + return "player" + if self.is_team: + return "team" + if self.is_referee: + return "referee" + return "unknown" + + @property + def team_id(self) -> str | None: + return self._entity.team_id() + + @property + def player_id(self) -> str | None: + return self._entity.player_id() + + @property + def referee_id(self) -> str | None: + return self._entity.player_id() if self.is_referee else None + + def entity_key(self) -> str: + """Returns a stable unique identifier for this entity (p_id|t_id).""" + p_id = "ball" + t_id = "none" + if self.is_player: + p_id = self.player_id or "unknown" + t_id = self.team_id or "none" + elif self.is_team: + t_id = self.team_id or "unknown" + p_id = f"team:{t_id}" + elif self.is_referee: + p_id = f"referee:{self.referee_id or 'unknown'}" + t_id = "none" + + # Normalize "none" strings + if t_id is None or str(t_id).lower() == "none" or str(t_id).lower() == "nan": + t_id = "none" + return f"{p_id}|{t_id}" + + def cdf_prefix(self) -> str: + """Returns the CDF prefix for this entity (e.g., 'teams/T1/players/P1/').""" + if self.is_ball: + return "ball/" + if self.is_team: + return f"teams/{self.team_id}/" + if self.is_referee: + return f"referees/{self.referee_id}/" + if self.is_player: + t_id = self.team_id + if t_id and str(t_id).lower() not in ("none", "nan"): + return f"teams/{t_id}/players/{self.player_id}/" + return f"other/{self.player_id}/" + return "" + + @staticmethod + def from_entity_key(key: str) -> TrackedEntity: + """Reconstructs a TrackedEntity from its entity key.""" + try: + parts = key.split("|") + p_id = parts[0] + t_id = parts[1] if len(parts) > 1 else "none" + except (ValueError, IndexError): + p_id, t_id = key, "none" + + if p_id == "ball": + return Ball() + if p_id.startswith("team:"): + team_id = t_id if t_id != "none" else p_id[5:] + return Team(team_id) + if p_id.startswith("referee:"): + return Referee(p_id[8:]) + + if t_id != "none": + return Player(t_id, p_id) + + # Handle players without a team + return TrackedEntity._from_inner(_DataField.from_name(f"other/{p_id}/x").entity()) + + @staticmethod + def _from_inner(entity: _TrackedEntity) -> TrackedEntity: + if entity.is_ball(): + return Ball._from_inner(entity) + if entity.is_player(): + return Player._from_inner(entity) + if entity.is_team(): + return Team._from_inner(entity) + if entity.is_referee(): + return Referee._from_inner(entity) + return TrackedEntity(entity) + + def __repr__(self) -> str: + return repr(self._entity) + + def __hash__(self) -> int: + return hash(self.entity_key()) + + +class Player(TrackedEntity): + def __init__(self, team_id: str, player_id: str) -> None: + self._entity = _DataField.from_name(f"teams/{team_id}/players/{player_id}/x").entity() + + @staticmethod + def _from_inner(entity: _TrackedEntity) -> Player: + instance = cast(Player, Player.__new__(Player)) + instance._entity = entity + return instance + + def __repr__(self) -> str: + return f"Player(team={self.team_id}, id={self.player_id})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Player): + return False + return self.team_id == other.team_id and self.player_id == other.player_id + + __hash__ = TrackedEntity.__hash__ + + +class Ball(TrackedEntity): + def __init__(self) -> None: + self._entity = _DataField.from_name("ball/x").entity() + + @staticmethod + def _from_inner(entity: _TrackedEntity) -> Ball: + instance = cast(Ball, Ball.__new__(Ball)) + instance._entity = entity + return instance + + def __repr__(self) -> str: + return "Ball" + + def __eq__(self, other: object) -> bool: + return isinstance(other, Ball) + + __hash__ = TrackedEntity.__hash__ + + +class Team(TrackedEntity): + def __init__(self, team_id: str) -> None: + self._entity = _DataField.from_name(f"teams/{team_id}/id").entity() + + @staticmethod + def _from_inner(entity: _TrackedEntity) -> Team: + instance = cast(Team, Team.__new__(Team)) + instance._entity = entity + return instance + + def __repr__(self) -> str: + return f"Team(id={self.team_id})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Team): + return False + return self.team_id == other.team_id + + __hash__ = TrackedEntity.__hash__ + + +class Referee(TrackedEntity): + def __init__(self, referee_id: str) -> None: + self._entity = _DataField.from_name(f"referees/{referee_id}/x").entity() + + @staticmethod + def _from_inner(entity: _TrackedEntity) -> Referee: + instance = cast(Referee, Referee.__new__(Referee)) + instance._entity = entity + return instance + + def __repr__(self) -> str: + return f"Referee(id={self.referee_id})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Referee): + return False + return self.referee_id == other.referee_id + + __hash__ = TrackedEntity.__hash__ + + +class DataField: + """Describes a single column/field in the tracking data.""" + + _field: _DataField + + def __new__( + cls, + name: str | None = None, + entity: TrackedEntity | None = None, + attribute: str | None = None, + ) -> DataField: + # Factory method to return correct subclass instance + if name is not None: + inner = _DataField.from_name(name) + elif entity is not None and attribute is not None: + path = f"{entity.cdf_prefix()}{attribute}" + inner = _DataField.from_name(path) + elif attribute is not None: + inner = _DataField.from_name(attribute) + else: + raise ValueError("Must provide either name or both entity and attribute") + + # Instantiate correct subclass + target_cls = FrameDataField if inner.is_frame() else EntityDataField + instance = object.__new__(target_cls) + instance._field = inner + return instance + + @staticmethod + def _from_inner(field: _DataField) -> DataField: + target_cls = FrameDataField if field.is_frame() else EntityDataField + instance = object.__new__(target_cls) + instance._field = field + return instance + + def name(self) -> str: + return self._field.name() + + @property + def entity(self) -> TrackedEntity | None: + inner = self._field.entity() + if inner is None: + return None + return TrackedEntity._from_inner(inner) + + @property + def attribute(self) -> str: + return self._field.attribute() + + @property + def team_id(self) -> str | None: + return getattr(self.entity, "team_id", None) + + @property + def player_id(self) -> str | None: + return getattr(self.entity, "player_id", None) + + def entity_key(self) -> str | None: + """Returns the stable entity key for this field, if applicable.""" + return self.entity.entity_key() if self.entity else None + + def cdf_prefix(self) -> str | None: + """Returns the CDF prefix for this field, if applicable.""" + return self.entity.cdf_prefix() if self.entity else None + + def __hash__(self) -> int: + return hash(self.name()) + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): + return self.name() == other + if not isinstance(other, DataField): + return False + return self.name() == other.name() + + def to_iceberg_name(self) -> str: + """Returns the mapped physical column name for Iceberg long storage format.""" + return str(getattr(self._field, "to_iceberg_name")()) + + def __repr__(self) -> str: + return f"DataField({self.name()})" + + +class FrameDataField(DataField): + pass + + +class EntityDataField(DataField): + pass + + +class SchemaId: + """Identifies a schema by (sport, data_kind, version). + + A schema is uniquely identified by a triple of (sport, data_kind, version). + For example, ``SchemaId("cdf", "tracking", "0.2.3")`` identifies the + CDF v0.2.3 standard tracking schema. + """ + + def __init__(self, sport: str, data_kind: str, version: str) -> None: + self._inner = _SchemaId(sport, data_kind, version) + + @staticmethod + def from_string(s: str) -> SchemaId: + """Parse a schema ID from a slash-separated string. + + Parameters + ---------- + s : str + A string in the format ``"sport/data_kind/version"``. + + Returns + ------- + SchemaId + """ + instance = SchemaId.__new__(SchemaId) + instance._inner = _SchemaId.from_string(s) + return instance + + @staticmethod + def _from_inner(inner: _SchemaId) -> SchemaId: + instance = SchemaId.__new__(SchemaId) + instance._inner = inner + return instance + + @property + def sport(self) -> str: + """The sport this schema applies to.""" + return self._inner.sport + + @property + def data_kind(self) -> str: + """The kind of data (e.g., 'tracking_2d', 'skeletal', 'gps').""" + return self._inner.data_kind + + @property + def version(self) -> str: + """The version string.""" + return self._inner.version + + def __str__(self) -> str: + return str(self._inner) + + def __repr__(self) -> str: + return f"SchemaId({self})" + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): + return str(self) == other + if not isinstance(other, SchemaId): + return False + return str(self) == str(other) + + def __hash__(self) -> int: + return hash(str(self)) + + +def list_schemas() -> list[str]: + """Returns a list of all registered schema identifiers. + + Returns + ------- + list[str] + A list of schema ID strings in the format ``"sport/data_kind/version"``. + """ + return _list_schemas() + + +class Fields: + """A collection of DataFields representing the schema of tracking data.""" + + def __init__(self, fields: _Fields | list[str]) -> None: + if isinstance(fields, list): + self._fields = _Fields(fields) + else: + self._fields = fields + + def __len__(self) -> int: + return len(self._fields) + + def __iter__(self) -> Iterator[DataField]: + for field in self._fields: + yield DataField._from_inner(field) + + def __getitem__(self, index: int) -> DataField: + return DataField._from_inner(self._fields[index]) + + def players(self, team: str | None = None) -> list[TrackedEntity]: + return [TrackedEntity._from_inner(p) for p in self._fields.player_entities(team)] + + def teams(self) -> list[TrackedEntity]: + return [TrackedEntity._from_inner(t) for t in self._fields.team_entities()] + + def referees(self) -> list[TrackedEntity]: + return [TrackedEntity._from_inner(r) for r in self._fields.referee_entities()] + + def names(self) -> list[str]: + """Returns a list of all column names in the schema.""" + return [f.name() for f in self] + + def match(self, pattern: str) -> list[str]: + """Returns a list of column names that match the given wildcard pattern.""" + import fnmatch + import re + + all_names = self.names() + if pattern in all_names: + return [pattern] + + # Support recursive wildcard ** + if "**" in pattern: + # Escape regex special characters manually + # but preserve glob characters * and / + reg = pattern + for char in ".+?()[]{}^$|": + reg = reg.replace(char, f"\\{char}") + + # Use placeholders to avoid conflicting replacements + # 1. Handle **/ specifically (optional intermediate levels) + reg = reg.replace("**/", "___REC_SLASH___") + # 2. Handle ** remaining + reg = reg.replace("**", "___REC___") + # 3. Handle standard * + reg = reg.replace("*", "___STD___") + + # Map placeholders to regex + reg = reg.replace("___REC_SLASH___", "(?:.*/)?") + reg = reg.replace("___REC___", ".*") + reg = reg.replace("___STD___", "[^/]*") + + regex = re.compile(f"^{reg}$") + return [n for n in all_names if regex.match(n)] + + # Standard fnmatch for simple patterns + return fnmatch.filter(all_names, pattern) + + def validate(self, schema: str | SchemaId | None = None) -> None: + """Validate the fields against a schema. + + Parameters + ---------- + schema : str | SchemaId | None, optional + The schema to validate against. If ``None``, validates against the + default schema (CDF v0.2.3 tracking). Can be a string in + the format ``"sport/data_kind/version"`` or a :class:`SchemaId`. + + Raises + ------ + SchemaError + If required fields are missing. + SchemaWarning + If unsupported fields are present (emitted as a warning). + """ + import warnings + + from .exceptions import SchemaError, SchemaWarning + + try: + if schema is not None: + if isinstance(schema, str): + schema = SchemaId.from_string(schema) + self._fields.validate_with_schema(schema._inner) + else: + self._fields.validate_with_schema(_SchemaId("cdf", "tracking", "0.2.3")) + except SchemaError: + raise + except Exception as e: # noqa: BLE001 + msg = str(e) + if "SchemaWarning" in type(e).__name__ or "unsupported" in msg.lower(): + warnings.warn(msg, SchemaWarning) + else: + raise SchemaError(msg) from e + + def validate_quiet(self, schema: str | SchemaId | None = None) -> bool: + """Validate the fields without raising errors. + + Parameters + ---------- + schema : str | SchemaId | None, optional + The schema to validate against. See :meth:`validate`. + + Returns + ------- + bool + ``True`` if valid, ``False`` otherwise. + """ + from .exceptions import SchemaError + + try: + self.validate(schema=schema) + return True + except SchemaError: + return False + + def __repr__(self) -> str: + return repr(self._fields) + + def __eq__(self, other: object) -> bool: + if isinstance(other, list): + return sorted([f.name() for f in self]) == sorted(other) + if not isinstance(other, Fields): + return False + return sorted([f.name() for f in self]) == sorted([f.name() for f in other]) diff --git a/python/tracking_data_container/selector.py b/python/tracking_data_container/selector.py new file mode 100644 index 0000000..4f04b0a --- /dev/null +++ b/python/tracking_data_container/selector.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from ._tracking_data_container import ( + ColumnSelector as _ColumnSelector, +) +from ._tracking_data_container import ( + Selection as _Selection, +) + +if TYPE_CHECKING: + pass + + +class Selection: + """A selection of columns in a TrackingDataContainer.""" + + def __init__(self, selection: _Selection) -> None: + self._selection = selection + + @property + def expr(self) -> Any: + return self._selection.expr + + @property + def _inner(self) -> _Selection: + return self._selection + + def __call__(self) -> Selection: + return self + + def attributes(self, names: str | list[str]) -> Selection: + """Filter the selection to only include specific attributes.""" + # This will be handled by our new Rust method if we expose it, + # but for now we'll just implement the high-level logic. + if isinstance(names, str): + names = [names] + + # We need to wrap the existing selection in an Attributes variant + # Since we don't have a direct constructor for PySelection from components, + # we'll rely on the Rust side providing an 'attributes' method on the Selection object. + return Selection(self._selection.attributes(names)) + + +class ColumnSelector: + """Provides a domain-specific way to select columns.""" + + def __init__(self, selector: _ColumnSelector) -> None: + self._selector = selector + + def ball(self) -> Selection: + """Selects all ball-related columns.""" + return Selection(self._selector.ball()) + + def players(self, team_id: str | None = None) -> Selection: + """Selects all player-related columns.""" + return Selection(self._selector.players(team_id)) + + def player(self, player_id: str, team_id: str | None = None) -> Selection: + """Selects all columns for a specific player.""" + return Selection(self._selector.player(player_id, team_id)) + + def team(self, team_id: str) -> Selection: + """Selects all columns for a specific team.""" + return Selection(self._selector.team(team_id)) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..132045c --- /dev/null +++ b/uv.lock @@ -0,0 +1,5910 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl", version = "1.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "yarl", version = "1.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d6/5aec9313ee6ea9c7cde8b891b69f4ff4001416867104580670a31daeba5b/aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7", size = 738950, upload-time = "2026-01-03T17:29:13.002Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/8fa90a7e6d11ff20a18837a8e2b5dd23db01aabc475aa9271c8ad33299f5/aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821", size = 496099, upload-time = "2026-01-03T17:29:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/b81f744d402510a8366b74eb420fc0cc1170d0c43daca12d10814df85f10/aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845", size = 491072, upload-time = "2026-01-03T17:29:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/56d1d1c0dd334cd203dd97706ce004c1aa24b34a813b0b8daf3383039706/aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af", size = 1671588, upload-time = "2026-01-03T17:29:18.539Z" }, + { url = "https://files.pythonhosted.org/packages/5f/34/8d7f962604f4bc2b4e39eb1220dac7d4e4cba91fb9ba0474b4ecd67db165/aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940", size = 1640334, upload-time = "2026-01-03T17:29:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/fcccf2c668d87337ddeef9881537baee13c58d8f01f12ba8a24215f2b804/aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160", size = 1722656, upload-time = "2026-01-03T17:29:22.531Z" }, + { url = "https://files.pythonhosted.org/packages/aa/98/c6f3b081c4c606bc1e5f2ec102e87d6411c73a9ef3616fea6f2d5c98c062/aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7", size = 1817625, upload-time = "2026-01-03T17:29:24.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c0/cfcc3d2e11b477f86e1af2863f3858c8850d751ce8dc39c4058a072c9e54/aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455", size = 1672604, upload-time = "2026-01-03T17:29:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/6b4ffcbcac4c6a5d041343a756f34a6dd26174ae07f977a64fe028dda5b0/aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279", size = 1554370, upload-time = "2026-01-03T17:29:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f0/e3ddfa93f17d689dbe014ba048f18e0c9f9b456033b70e94349a2e9048be/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e", size = 1642023, upload-time = "2026-01-03T17:29:30.002Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/c14019c9ec60a8e243d06d601b33dcc4fd92379424bde3021725859d7f99/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d", size = 1649680, upload-time = "2026-01-03T17:29:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fd/09c9451dae5aa5c5ed756df95ff9ef549d45d4be663bafd1e4954fd836f0/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808", size = 1692407, upload-time = "2026-01-03T17:29:33.392Z" }, + { url = "https://files.pythonhosted.org/packages/a6/81/938bc2ec33c10efd6637ccb3d22f9f3160d08e8f3aa2587a2c2d5ab578eb/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40", size = 1543047, upload-time = "2026-01-03T17:29:34.855Z" }, + { url = "https://files.pythonhosted.org/packages/f7/23/80488ee21c8d567c83045e412e1d9b7077d27171591a4eb7822586e8c06a/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29", size = 1715264, upload-time = "2026-01-03T17:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/e2/83/259a8da6683182768200b368120ab3deff5370bed93880fb9a3a86299f34/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11", size = 1657275, upload-time = "2026-01-03T17:29:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4f/2c41f800a0b560785c10fb316216ac058c105f9be50bdc6a285de88db625/aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd", size = 434053, upload-time = "2026-01-03T17:29:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/80/df/29cd63c7ecfdb65ccc12f7d808cac4fa2a19544660c06c61a4a48462de0c/aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c", size = 456687, upload-time = "2026-01-03T17:29:41.819Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, + { url = "https://files.pythonhosted.org/packages/bf/79/446655656861d3e7e2c32bfcf160c7aa9e9dc63776a691b124dba65cdd77/aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e", size = 741433, upload-time = "2026-01-03T17:32:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/cb/49/773c4b310b5140d2fb5e79bb0bf40b7b41dad80a288ca1a8759f5f72bda9/aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7", size = 497332, upload-time = "2026-01-03T17:32:28.37Z" }, + { url = "https://files.pythonhosted.org/packages/bc/31/1dcbc4b83a4e6f76a0ad883f07f21ffbfe29750c89db97381701508c9f45/aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02", size = 492365, upload-time = "2026-01-03T17:32:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b5/b50657496c8754482cd7964e50aaf3aa84b3db61ed45daec4c1aec5b94b4/aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43", size = 1660440, upload-time = "2026-01-03T17:32:32.586Z" }, + { url = "https://files.pythonhosted.org/packages/2a/73/9b69e5139d89d75127569298931444ad78ea86a5befd5599780b1e9a6880/aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6", size = 1632740, upload-time = "2026-01-03T17:32:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fe/3ea9b5af694b4e3aec0d0613a806132ca744747146fca68e96bf056f61a7/aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce", size = 1719782, upload-time = "2026-01-03T17:32:37.737Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/46b3b06e60851cbb71efb0f79a3267279cbef7b12c58e68a1e897f269cca/aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80", size = 1813527, upload-time = "2026-01-03T17:32:39.973Z" }, + { url = "https://files.pythonhosted.org/packages/36/23/71ceb78c769ed65fe4c697692de232b63dab399210678d2b00961ccb0619/aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a", size = 1661268, upload-time = "2026-01-03T17:32:42.082Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/86e929523d955e85ebab7c0e2b9e0cb63604cfc27dc3280e10d0063cf682/aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6", size = 1552742, upload-time = "2026-01-03T17:32:44.622Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/3f5987cba1bab6bd151f0d97aa60f0ce04d3c83316692a6bb6ba2fb69f92/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558", size = 1632918, upload-time = "2026-01-03T17:32:46.749Z" }, + { url = "https://files.pythonhosted.org/packages/be/2c/7e1e85121f2e31ee938cb83a8f32dfafd4908530c10fabd6d46761c12ac7/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7", size = 1644446, upload-time = "2026-01-03T17:32:49.063Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/ce6133d423ad0e8ca976a7c848f7146bca3520eea4ccf6b95e2d077c9d20/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877", size = 1689487, upload-time = "2026-01-03T17:32:51.113Z" }, + { url = "https://files.pythonhosted.org/packages/50/f7/ff7a27c15603d460fd1366b3c22054f7ae4fa9310aca40b43bde35867fcd/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3", size = 1540715, upload-time = "2026-01-03T17:32:53.38Z" }, + { url = "https://files.pythonhosted.org/packages/17/02/053f11346e5b962e6d8a1c4f8c70c29d5970a1b4b8e7894c68e12c27a57f/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704", size = 1711835, upload-time = "2026-01-03T17:32:56.088Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/9b9761ddf276fd6708d13720197cbac19b8d67ecfa9116777924056cfcaa/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f", size = 1649593, upload-time = "2026-01-03T17:32:58.181Z" }, + { url = "https://files.pythonhosted.org/packages/ae/72/5d817e9ea218acae12a5e3b9ad1178cf0c12fc3570c0b47eea2daf95f9ea/aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1", size = 434831, upload-time = "2026-01-03T17:33:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/39/cb/22659d9bf3149b7a2927bc2769cc9c8f8f5a80eba098398e03c199a43a85/aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538", size = 457697, upload-time = "2026-01-03T17:33:03.167Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, + { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, + { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, + { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/21/a2b1505639008ba2e6ef03733a81fc6cfd6a07ea6139a2b76421230b8dad/charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765", size = 283319, upload-time = "2026-03-06T06:00:26.433Z" }, + { url = "https://files.pythonhosted.org/packages/70/67/df234c29b68f4e1e095885c9db1cb4b69b8aba49cf94fac041db4aaf1267/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990", size = 189974, upload-time = "2026-03-06T06:00:28.222Z" }, + { url = "https://files.pythonhosted.org/packages/df/7f/fc66af802961c6be42e2c7b69c58f95cbd1f39b0e81b3365d8efe2a02a04/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2", size = 207866, upload-time = "2026-03-06T06:00:29.769Z" }, + { url = "https://files.pythonhosted.org/packages/c9/23/404eb36fac4e95b833c50e305bba9a241086d427bb2167a42eac7c4f7da4/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765", size = 203239, upload-time = "2026-03-06T06:00:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2f/8a1d989bfadd120c90114ab33e0d2a0cbde05278c1fc15e83e62d570f50a/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d", size = 196529, upload-time = "2026-03-06T06:00:32.608Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0c/c75f85ff7ca1f051958bb518cd43922d86f576c03947a050fbedfdfb4f15/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8", size = 184152, upload-time = "2026-03-06T06:00:33.93Z" }, + { url = "https://files.pythonhosted.org/packages/f9/20/4ed37f6199af5dde94d4aeaf577f3813a5ec6635834cda1d957013a09c76/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412", size = 195226, upload-time = "2026-03-06T06:00:35.469Z" }, + { url = "https://files.pythonhosted.org/packages/28/31/7ba1102178cba7c34dcc050f43d427172f389729e356038f0726253dd914/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2", size = 192933, upload-time = "2026-03-06T06:00:36.83Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/f86443ab3921e6a60b33b93f4a1161222231f6c69bc24fb18f3bee7b8518/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1", size = 185647, upload-time = "2026-03-06T06:00:38.367Z" }, + { url = "https://files.pythonhosted.org/packages/82/44/08b8be891760f1f5a6d23ce11d6d50c92981603e6eb740b4f72eea9424e2/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4", size = 209533, upload-time = "2026-03-06T06:00:41.931Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/df114f23406199f8af711ddccfbf409ffbc5b7cdc18fa19644997ff0c9bb/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f", size = 195901, upload-time = "2026-03-06T06:00:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/07/83/71ef34a76fe8aa05ff8f840244bda2d61e043c2ef6f30d200450b9f6a1be/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550", size = 204950, upload-time = "2026-03-06T06:00:45.202Z" }, + { url = "https://files.pythonhosted.org/packages/58/40/0253be623995365137d7dc68e45245036207ab2227251e69a3d93ce43183/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2", size = 198546, upload-time = "2026-03-06T06:00:46.481Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5c/5f3cb5b259a130895ef5ae16b38eaf141430fa3f7af50cd06c5d67e4f7b2/charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475", size = 132516, upload-time = "2026-03-06T06:00:47.924Z" }, + { url = "https://files.pythonhosted.org/packages/a5/c3/84fb174e7770f2df2e1a2115090771bfbc2227fb39a765c6d00568d1aab4/charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05", size = 142906, upload-time = "2026-03-06T06:00:49.389Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b2/6f852f8b969f2cbd0d4092d2e60139ab1af95af9bb651337cae89ec0f684/charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064", size = 133258, upload-time = "2026-03-06T06:00:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, + { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, + { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, + { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, + { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, + { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, + { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, + { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, + { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, + { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/be/76/96dec962aa996081c48f544d5e9e97322006a1e67e8f76bad41f3fb0b151/charset_normalizer-3.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:259cd1ca995ad525f638e131dbcc2353a586564c038fc548a3fe450a91882139", size = 283220, upload-time = "2026-03-06T06:02:53.024Z" }, + { url = "https://files.pythonhosted.org/packages/cc/80/050c340587611be9743eff02d1ca34b5fc76a4356849dcb74dfd898d6d87/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a28afb04baa55abf26df544e3e5c6534245d3daa5178bc4a8eeb48202060d0e", size = 189988, upload-time = "2026-03-06T06:02:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a3/bb6caf9f5544ccaaca5c7e387fa868868d3420bcb03e8bc30f37be2e8a72/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff95a9283de8a457e6b12989de3f9f5193430f375d64297d323a615ea52cbdb3", size = 207786, upload-time = "2026-03-06T06:02:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/50/e56713141f2fdb3a4d46092425d58dc97a48e1e10ce321ac6ba43862aacf/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:708c7acde173eedd4bfa4028484426ba689d2103b28588c513b9db2cd5ecde9c", size = 203556, upload-time = "2026-03-06T06:02:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/22/34/ed0cfd388dd9106725afc2beb036adbaa167fc0b5a9ee8cd3940757fb060/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa92ec1102eaff840ccd1021478af176a831f1bccb08e526ce844b7ddda85c22", size = 196552, upload-time = "2026-03-06T06:02:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8b/da4a4c3d26c539fdd777cfbd2c0d83e77e1218879517ef91c4ece7238563/charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:5fea359734b140d0d6741189fea5478c6091b54ffc69d7ce119e0a05637d8c99", size = 184289, upload-time = "2026-03-06T06:03:00.448Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/9f67c1f94ea9ae1e08c8fa2182b1f5411732e18643e7080fc8c10ba1e021/charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e545b51da9f9af5c67815ca0eb40676c0f016d0b0381c86f20451e35696c5f95", size = 195282, upload-time = "2026-03-06T06:03:02.161Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/aaf84a2e37e75470640e965d6619c6d9a521eb7c8aa097f2586907859198/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:30987f4a8ed169983f93e1be8ffeea5214a779e27ed0b059835c7afe96550ad7", size = 192889, upload-time = "2026-03-06T06:03:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/9b714873baf9a841613e8b49a5a3cd77d985d2c6c80f5038a5057395ebac/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:149ec69866c3d6c2fb6f758dbc014ecb09f30b35a5ca90b6a8a2d4e54e18fdfe", size = 185738, upload-time = "2026-03-06T06:03:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e5/bf57e1a9210a6ba78c740d66d05165a55b2cbeca29a83b8c659c9eb2d6c6/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:530beedcec9b6e027e7a4b6ce26eed36678aa39e17da85e6e03d7bd9e8e9d7c9", size = 209458, upload-time = "2026-03-06T06:03:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/65/91/3c8cb46d840840f2593028fd708ea50695f8f61e1c490530ef1cce824f56/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:14498a429321de554b140013142abe7608f9d8ccc04d7baf2ad60498374aefa2", size = 195792, upload-time = "2026-03-06T06:03:08Z" }, + { url = "https://files.pythonhosted.org/packages/b0/43/783be5c6932fa8846a98313a2242fbcfe0c06c1c0ac2d6856b99d93069eb/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2820a98460c83663dd8ec015d9ddfd1e4879f12e06bb7d0500f044fb477d2770", size = 204829, upload-time = "2026-03-06T06:03:09.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/7d/138b5311c32fd24396321db796538cc748287c92da5e6fc1996babc06f99/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:aa2f963b4da26daf46231d9b9e0e2c9408a751f8f0d0f44d2de56d3caf51d294", size = 198558, upload-time = "2026-03-06T06:03:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/9c/87/ddd8bbdd703707c019fe9d14b678011627e6c5131dfdefe42aff151d718c/charset_normalizer-3.4.5-cp39-cp39-win32.whl", hash = "sha256:82cc7c2ad42faec8b574351f8bc2a0c049043893853317bd9bb309f5aba6cb5a", size = 132370, upload-time = "2026-03-06T06:03:13.327Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/d7cd28ae6d4dd47170b95153986789d69af4d5844f640edbc5138e4a70a2/charset_normalizer-3.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:92263f7eca2f4af326cd20de8d16728d2602f7cfea02e790dcde9d83c365d7cc", size = 142877, upload-time = "2026-03-06T06:03:15.041Z" }, + { url = "https://files.pythonhosted.org/packages/9c/26/8d68681566f288998eb36a0c60dd2c5c8aa93ee67b0d7e3dc72606650828/charset_normalizer-3.4.5-cp39-cp39-win_arm64.whl", hash = "sha256:014837af6fabf57121b6254fa8ade10dceabc3528b27b721a64bbc7b8b1d4eb4", size = 133186, upload-time = "2026-03-06T06:03:16.476Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370, upload-time = "2024-08-27T21:00:03.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366, upload-time = "2024-08-27T20:50:09.947Z" }, + { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226, upload-time = "2024-08-27T20:50:16.1Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460, upload-time = "2024-08-27T20:50:22.536Z" }, + { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623, upload-time = "2024-08-27T20:50:28.806Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761, upload-time = "2024-08-27T20:50:35.126Z" }, + { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015, upload-time = "2024-08-27T20:50:40.318Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672, upload-time = "2024-08-27T20:50:55.643Z" }, + { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688, upload-time = "2024-08-27T20:51:11.293Z" }, + { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145, upload-time = "2024-08-27T20:51:15.2Z" }, + { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019, upload-time = "2024-08-27T20:51:19.365Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356, upload-time = "2024-08-27T20:51:24.146Z" }, + { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915, upload-time = "2024-08-27T20:51:28.683Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443, upload-time = "2024-08-27T20:51:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548, upload-time = "2024-08-27T20:51:39.322Z" }, + { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118, upload-time = "2024-08-27T20:51:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162, upload-time = "2024-08-27T20:51:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396, upload-time = "2024-08-27T20:52:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297, upload-time = "2024-08-27T20:52:21.843Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808, upload-time = "2024-08-27T20:52:25.163Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181, upload-time = "2024-08-27T20:52:29.13Z" }, + { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838, upload-time = "2024-08-27T20:52:33.911Z" }, + { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549, upload-time = "2024-08-27T20:52:39.179Z" }, + { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177, upload-time = "2024-08-27T20:52:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735, upload-time = "2024-08-27T20:52:51.05Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679, upload-time = "2024-08-27T20:52:58.473Z" }, + { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549, upload-time = "2024-08-27T20:53:06.593Z" }, + { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068, upload-time = "2024-08-27T20:53:23.442Z" }, + { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833, upload-time = "2024-08-27T20:53:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681, upload-time = "2024-08-27T20:53:43.05Z" }, + { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283, upload-time = "2024-08-27T20:53:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879, upload-time = "2024-08-27T20:53:51.597Z" }, + { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573, upload-time = "2024-08-27T20:53:55.659Z" }, + { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184, upload-time = "2024-08-27T20:54:00.225Z" }, + { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262, upload-time = "2024-08-27T20:54:05.234Z" }, + { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806, upload-time = "2024-08-27T20:54:09.889Z" }, + { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710, upload-time = "2024-08-27T20:54:14.536Z" }, + { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107, upload-time = "2024-08-27T20:54:29.735Z" }, + { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458, upload-time = "2024-08-27T20:54:45.507Z" }, + { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643, upload-time = "2024-08-27T20:55:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301, upload-time = "2024-08-27T20:55:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972, upload-time = "2024-08-27T20:54:50.347Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375, upload-time = "2024-08-27T20:54:54.909Z" }, + { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188, upload-time = "2024-08-27T20:55:00.184Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644, upload-time = "2024-08-27T20:55:05.673Z" }, + { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141, upload-time = "2024-08-27T20:55:11.047Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469, upload-time = "2024-08-27T20:55:15.914Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894, upload-time = "2024-08-27T20:55:31.553Z" }, + { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829, upload-time = "2024-08-27T20:55:47.837Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/b9f72758adb6ef7397327ceb8b9c39c75711affb220e4f53c745ea1d5a9a/contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8", size = 265518, upload-time = "2024-08-27T20:56:01.333Z" }, + { url = "https://files.pythonhosted.org/packages/ec/22/19f5b948367ab5260fb41d842c7a78dae645603881ea6bc39738bcfcabf6/contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c", size = 249350, upload-time = "2024-08-27T20:56:05.432Z" }, + { url = "https://files.pythonhosted.org/packages/26/76/0c7d43263dd00ae21a91a24381b7e813d286a3294d95d179ef3a7b9fb1d7/contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca", size = 309167, upload-time = "2024-08-27T20:56:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/96/3b/cadff6773e89f2a5a492c1a8068e21d3fccaf1a1c1df7d65e7c8e3ef60ba/contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f", size = 348279, upload-time = "2024-08-27T20:56:15.41Z" }, + { url = "https://files.pythonhosted.org/packages/e1/86/158cc43aa549d2081a955ab11c6bdccc7a22caacc2af93186d26f5f48746/contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc", size = 318519, upload-time = "2024-08-27T20:56:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/05/11/57335544a3027e9b96a05948c32e566328e3a2f84b7b99a325b7a06d2b06/contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2", size = 321922, upload-time = "2024-08-27T20:56:26.983Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/02114f96543f4a1b694333b92a6dcd4f8eebbefcc3a5f3bbb1316634178f/contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e", size = 1258017, upload-time = "2024-08-27T20:56:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3b/bfe4c81c6d5881c1c643dde6620be0b42bf8aab155976dd644595cfab95c/contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800", size = 1316773, upload-time = "2024-08-27T20:56:58.58Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353, upload-time = "2024-08-27T20:57:02.718Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817, upload-time = "2024-08-27T20:57:06.328Z" }, + { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886, upload-time = "2024-08-27T20:57:10.863Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008, upload-time = "2024-08-27T20:57:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690, upload-time = "2024-08-27T20:57:19.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/dc3dcd77ac7460ab7e9d2b01a618cb31406902e50e605a8d6091f0a8f7cc/contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0", size = 261894, upload-time = "2024-08-27T20:57:23.873Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/531642a01cfec39d1682e46b5457b07cf805e3c3c584ec27e2a6223f8f6c/contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102", size = 311099, upload-time = "2024-08-27T20:57:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838, upload-time = "2024-08-27T20:57:32.913Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/be/8bd693a0b9d53d48c8978fa5d889e06f3b5b03e45fd1ea1e78267b4887cb/debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64", size = 2099192, upload-time = "2026-01-29T23:03:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/77/1b/85326d07432086a06361d493d2743edd0c4fc2ef62162be7f8618441ac37/debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642", size = 3088568, upload-time = "2026-01-29T23:03:31.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/60/3e08462ee3eccd10998853eb35947c416e446bfe2bc37dbb886b9044586c/debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2", size = 5284399, upload-time = "2026-01-29T23:03:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/09d49106e770fe558ced5e80df2e3c2ebee10e576eda155dcc5670473663/debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893", size = 5316388, upload-time = "2026-01-29T23:03:35.095Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6b/668f21567e3250463beb6a401e7d598baa2a0907224000d7f68b9442c243/debugpy-1.8.20-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:bff8990f040dacb4c314864da95f7168c5a58a30a66e0eea0fb85e2586a92cd6", size = 2100484, upload-time = "2026-01-29T23:04:09.929Z" }, + { url = "https://files.pythonhosted.org/packages/cf/49/223143d1da586b891f35a45515f152742ad85bfc10d2e02e697f65c83b32/debugpy-1.8.20-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:70ad9ae09b98ac307b82c16c151d27ee9d68ae007a2e7843ba621b5ce65333b5", size = 3081272, upload-time = "2026-01-29T23:04:11.664Z" }, + { url = "https://files.pythonhosted.org/packages/b1/24/9f219c9290fe8bee4f63f9af8ebac440c802e6181d7f39a79abcb5fdff2f/debugpy-1.8.20-cp39-cp39-win32.whl", hash = "sha256:9eeed9f953f9a23850c85d440bf51e3c56ed5d25f8560eeb29add815bd32f7ee", size = 5285196, upload-time = "2026-01-29T23:04:13.105Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f3/4a12d7b1b09e3b79ba6e3edfa0c677b8b8bdf110bc4b3607e0f29fb4e8b3/debugpy-1.8.20-cp39-cp39-win_amd64.whl", hash = "sha256:760813b4fff517c75bfe7923033c107104e76acfef7bda011ffea8736e9a66f8", size = 5317163, upload-time = "2026-01-29T23:04:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fast-forward-football" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "kloppy", marker = "python_full_version == '3.11.*'" }, + { name = "polars", marker = "python_full_version == '3.11.*'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/e1/157d8c3845fb6239e6bb323b4b0062b695ecb9746ed56062ed2625ada3ba/fast_forward_football-0.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:109e69d6eacb8d844a37d45a8ccf5903654340fdf7d6dac2c354fce3f537c98f", size = 7197771, upload-time = "2026-02-26T08:22:49.127Z" }, + { url = "https://files.pythonhosted.org/packages/be/97/a4eeda701391eed637922a14a27f7d3d99e2052070d033da3dd576e24535/fast_forward_football-0.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f840accebfac65237755100ce0dd34a1dbdb4d26bc8d7cb4c6ae2766a6b2e63", size = 6629103, upload-time = "2026-02-26T08:22:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/c0751a6f2b95fbc114d0fe1733bb795d6c2032f2e7e965de42a1dec39290/fast_forward_football-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82e5f5b8d8973df894d272e6062a6a01090d87914f264e888c7a05b985b44ace", size = 7423194, upload-time = "2026-02-26T08:22:54.308Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2b/3cad264eb52304e4c5a898566f892d02019f5d141ec6da970c956aaa5e41/fast_forward_football-0.1.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:743490b3cff6bbe70107bbec064e48786c468958d2db10dbec721ec316d46e6e", size = 6316701, upload-time = "2026-02-26T08:22:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a8/3278e57ae6877d1d390069db43f96f8ae36feca4f522f4f2219643c75da2/fast_forward_football-0.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:2de41b9548a0080b7604225c0aeaf45653c64912128bc247b205be2a6769fb13", size = 7449318, upload-time = "2026-02-26T08:23:00.234Z" }, + { url = "https://files.pythonhosted.org/packages/e7/74/7807b18b2c4e020bce01f7ab61b3bcaa1c8b29b059d649b583ad8f5db377/fast_forward_football-0.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:99b8330633f8cab625a244e3af021002c30123632b6718b4ef970ad05f5057f2", size = 7228402, upload-time = "2026-02-26T08:23:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/80/e1/505a2990dea6a47fe68ef873922e68dcdf302f273590dc2f6c2dc9c91a73/fast_forward_football-0.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:632314ef1ab8768e0ea6ec10bcc4e06579bdfaec88340facf4dbd16a60efae9b", size = 6621155, upload-time = "2026-02-26T08:23:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/76/e9/97f6270545abe6928184c58220f5bd103968a6834dddb1da7c1b1df112d8/fast_forward_football-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1ce584f8ff47c6e9f551e7e15f04ae028243f3770208820a25622abfe5f20b1", size = 7416055, upload-time = "2026-02-26T08:23:07.429Z" }, + { url = "https://files.pythonhosted.org/packages/19/a6/45a2e311a8d31ebf4c2caa1c20886ebdf0b11cbd7ae54f3f9246d3d2ccc5/fast_forward_football-0.1.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8d9562fd39553e9fb93fb7eacf4a3f23a86158896764c59c74842b58ada33f1c", size = 6314738, upload-time = "2026-02-26T08:23:09.895Z" }, + { url = "https://files.pythonhosted.org/packages/e7/02/cfb8ca068f1cadda2cf826d241a522c06ce28f5dd429fb0578004a3e9cb4/fast_forward_football-0.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:188a9c3bfbdd2ad3850431b2bd2ed3cf2cb9ddbbb88e407b5bd3e6830284b1a9", size = 7442152, upload-time = "2026-02-26T08:23:12.174Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4d/359b3102472f676908d2fa20db0d038c7ffc178e560d7fac032c10993042/fast_forward_football-0.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4d87517dc3741287aeb5ed80860577bd1cca7295853a870fe7093fa7f1fcdd23", size = 7227906, upload-time = "2026-02-26T08:23:14.837Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/8a238fbcd2604eccf1c9cf121f911c9f5e21f401befe5d15f732297f0207/fast_forward_football-0.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30a67414b61a086c3387858e3e3ac64bce4e9e154e0e5c262d56d0c8ea8908f7", size = 6621009, upload-time = "2026-02-26T08:23:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/e5/82/29cb557af2ab57c9900b1d535c66707dae604ba42492060e6cffc07b5925/fast_forward_football-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1ecf259886fd455961960366ae4aa7b35349d34eff938baedb5cb9847a95f0", size = 7415125, upload-time = "2026-02-26T08:23:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b8/05d73f0c74de201716e3f0bb7e8fbf7853b11b27df2e9b055a6a30aac196/fast_forward_football-0.1.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:cdf629decf6b8539ee7c0b620e92cc771427ac5aee69f50944592d6c8f99ac77", size = 6313550, upload-time = "2026-02-26T08:23:22.155Z" }, + { url = "https://files.pythonhosted.org/packages/ca/28/70681125529ca8f7b629120b6be5be4f53aab42a6b939b8a5c139ace7441/fast_forward_football-0.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2bc2431588f238a7a4d9c25b609a6631d81b9f83d1f83883329cc7eed7b609cd", size = 7441015, upload-time = "2026-02-26T08:23:24.371Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, +] + +[[package]] +name = "fonttools" +version = "4.60.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/c4/db6a7b5eb0656534c3aa2596c2c5e18830d74f1b9aa5aa8a7dff63a0b11d/fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c", size = 3562254, upload-time = "2025-12-09T13:38:11.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/de/9e10a99fb3070accb8884886a41a4ce54e49bf2fa4fc63f48a6cf2061713/fonttools-4.60.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e36fadcf7e8ca6e34d490eef86ed638d6fd9c55d2f514b05687622cfc4a7050", size = 2850403, upload-time = "2025-12-09T13:35:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/e4/40/d5b369d1073b134f600a94a287e13b5bdea2191ba6347d813fa3da00e94a/fonttools-4.60.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e500fc9c04bee749ceabfc20cb4903f6981c2139050d85720ea7ada61b75d5c", size = 2398629, upload-time = "2025-12-09T13:35:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b5/123819369aaf99d1e4dc49f1de1925d4edc7379114d15a56a7dd2e9d56e6/fonttools-4.60.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22efea5e784e1d1cd8d7b856c198e360a979383ebc6dea4604743b56da1cbc34", size = 4893471, upload-time = "2025-12-09T13:35:58.927Z" }, + { url = "https://files.pythonhosted.org/packages/24/29/f8f8acccb9716b899be4be45e9ce770d6aa76327573863e68448183091b0/fonttools-4.60.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:677aa92d84d335e4d301d8ba04afca6f575316bc647b6782cb0921943fcb6343", size = 4854686, upload-time = "2025-12-09T13:36:01.767Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0d/f3f51d7519f44f2dd5c9a60d7cd41185ebcee4348f073e515a3a93af15ff/fonttools-4.60.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edd49d3defbf35476e78b61ff737ff5efea811acff68d44233a95a5a48252334", size = 4871233, upload-time = "2025-12-09T13:36:06.094Z" }, + { url = "https://files.pythonhosted.org/packages/cc/3f/4d4fd47d3bc40ab4d76718555185f8adffb5602ea572eac4bbf200c47d22/fonttools-4.60.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:126839492b69cecc5baf2bddcde60caab2ffafd867bbae2a88463fce6078ca3a", size = 4988936, upload-time = "2025-12-09T13:36:08.42Z" }, + { url = "https://files.pythonhosted.org/packages/01/6f/83bbdefa43f2c3ae206fd8c4b9a481f3c913eef871b1ce9a453069239e39/fonttools-4.60.2-cp310-cp310-win32.whl", hash = "sha256:ffcab6f5537136046ca902ed2491ab081ba271b07591b916289b7c27ff845f96", size = 2278044, upload-time = "2025-12-09T13:36:10.641Z" }, + { url = "https://files.pythonhosted.org/packages/d4/04/7d9a137e919d6c9ef26704b7f7b2580d9cfc5139597588227aacebc0e3b7/fonttools-4.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:9c68b287c7ffcd29dd83b5f961004b2a54a862a88825d52ea219c6220309ba45", size = 2326522, upload-time = "2025-12-09T13:36:12.981Z" }, + { url = "https://files.pythonhosted.org/packages/e0/80/b7693d37c02417e162cc83cdd0b19a4f58be82c638b5d4ce4de2dae050c4/fonttools-4.60.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2aed0a7931401b3875265717a24c726f87ecfedbb7b3426c2ca4d2812e281ae", size = 2847809, upload-time = "2025-12-09T13:36:14.884Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9a/9c2c13bf8a6496ac21607d704e74e9cc68ebf23892cf924c9a8b5c7566b9/fonttools-4.60.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea6868e9d2b816c9076cfea77754686f3c19149873bdbc5acde437631c15df1", size = 2397302, upload-time = "2025-12-09T13:36:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/56/f6/ce38ff6b2d2d58f6fd981d32f3942365bfa30eadf2b47d93b2d48bf6097f/fonttools-4.60.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa27f34950aa1fe0f0b1abe25eed04770a3b3b34ad94e5ace82cc341589678a", size = 5054418, upload-time = "2025-12-09T13:36:19.062Z" }, + { url = "https://files.pythonhosted.org/packages/88/06/5353bea128ff39e857c31de3dd605725b4add956badae0b31bc9a50d4c8e/fonttools-4.60.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13a53d479d187b09bfaa4a35ffcbc334fc494ff355f0a587386099cb66674f1e", size = 5031652, upload-time = "2025-12-09T13:36:21.206Z" }, + { url = "https://files.pythonhosted.org/packages/71/05/ebca836437f6ebd57edd6428e7eff584e683ff0556ddb17d62e3b731f46c/fonttools-4.60.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fac5e921d3bd0ca3bb8517dced2784f0742bc8ca28579a68b139f04ea323a779", size = 5030321, upload-time = "2025-12-09T13:36:23.515Z" }, + { url = "https://files.pythonhosted.org/packages/57/f9/eb9d2a2ce30c99f840c1cc3940729a970923cf39d770caf88909d98d516b/fonttools-4.60.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:648f4f9186fd7f1f3cd57dbf00d67a583720d5011feca67a5e88b3a491952cfb", size = 5154255, upload-time = "2025-12-09T13:36:25.879Z" }, + { url = "https://files.pythonhosted.org/packages/08/a2/088b6ceba8272a9abb629d3c08f9c1e35e5ce42db0ccfe0c1f9f03e60d1d/fonttools-4.60.2-cp311-cp311-win32.whl", hash = "sha256:3274e15fad871bead5453d5ce02658f6d0c7bc7e7021e2a5b8b04e2f9e40da1a", size = 2276300, upload-time = "2025-12-09T13:36:27.772Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/8e4c3d908cc5dade7bb1316ce48589f6a24460c1056fd4b8db51f1fa309a/fonttools-4.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:91d058d5a483a1525b367803abb69de0923fbd45e1f82ebd000f5c8aa65bc78e", size = 2327574, upload-time = "2025-12-09T13:36:30.89Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/530c9eddcd1c39219dc0aaede2b5a4c8ab80e0bb88d1b3ffc12944c4aac3/fonttools-4.60.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e0164b7609d2b5c5dd4e044b8085b7bd7ca7363ef8c269a4ab5b5d4885a426b2", size = 2847196, upload-time = "2025-12-09T13:36:33.262Z" }, + { url = "https://files.pythonhosted.org/packages/19/2f/4077a482836d5bbe3bc9dac1c004d02ee227cf04ed62b0a2dfc41d4f0dfd/fonttools-4.60.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dd3d9574fc595c1e97faccae0f264dc88784ddf7fbf54c939528378bacc0033", size = 2395842, upload-time = "2025-12-09T13:36:35.47Z" }, + { url = "https://files.pythonhosted.org/packages/dd/05/aae5bb99c5398f8ed4a8b784f023fd9dd3568f0bd5d5b21e35b282550f11/fonttools-4.60.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98d0719f1b11c2817307d2da2e94296a3b2a3503f8d6252a101dca3ee663b917", size = 4949713, upload-time = "2025-12-09T13:36:37.874Z" }, + { url = "https://files.pythonhosted.org/packages/b4/37/49067349fc78ff0efbf09fadefe80ddf41473ca8f8a25400e3770da38328/fonttools-4.60.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d3ea26957dd07209f207b4fff64c702efe5496de153a54d3b91007ec28904dd", size = 4999907, upload-time = "2025-12-09T13:36:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/16/31/d0f11c758bd0db36b664c92a0f9dfdcc2d7313749aa7d6629805c6946f21/fonttools-4.60.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ee301273b0850f3a515299f212898f37421f42ff9adfc341702582ca5073c13", size = 4939717, upload-time = "2025-12-09T13:36:43.075Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bc/1cff0d69522e561bf1b99bee7c3911c08c25e919584827c3454a64651ce9/fonttools-4.60.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6eb4694cc3b9c03b7c01d65a9cf35b577f21aa6abdbeeb08d3114b842a58153", size = 5089205, upload-time = "2025-12-09T13:36:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e6/fb174f0069b7122e19828c551298bfd34fdf9480535d2a6ac2ed37afacd3/fonttools-4.60.2-cp312-cp312-win32.whl", hash = "sha256:57f07b616c69c244cc1a5a51072eeef07dddda5ebef9ca5c6e9cf6d59ae65b70", size = 2264674, upload-time = "2025-12-09T13:36:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/75/57/6552ffd6b582d3e6a9f01780c5275e6dfff1e70ca146101733aa1c12a129/fonttools-4.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:310035802392f1fe5a7cf43d76f6ff4a24c919e4c72c0352e7b8176e2584b8a0", size = 2314701, upload-time = "2025-12-09T13:36:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e4/8381d0ca6b6c6c484660b03517ec5b5b81feeefca3808726dece36c652a9/fonttools-4.60.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bb5fd231e56ccd7403212636dcccffc96c5ae0d6f9e4721fa0a32cb2e3ca432", size = 2842063, upload-time = "2025-12-09T13:36:53.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2c/4367117ee8ff4f4374787a1222da0bd413d80cf3522111f727a7b8f80d1d/fonttools-4.60.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:536b5fab7b6fec78ccf59b5c59489189d9d0a8b0d3a77ed1858be59afb096696", size = 2393792, upload-time = "2025-12-09T13:36:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/49/b7/a76b6dffa193869e54e32ca2f9abb0d0e66784bc8a24e6f86eb093015481/fonttools-4.60.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b9288fc38252ac86a9570f19313ecbc9ff678982e0f27c757a85f1f284d3400", size = 4924020, upload-time = "2025-12-09T13:36:58.229Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4e/0078200e2259f0061c86a74075f507d64c43dd2ab38971956a5c0012d344/fonttools-4.60.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93fcb420791d839ef592eada2b69997c445d0ce9c969b5190f2e16828ec10607", size = 4980070, upload-time = "2025-12-09T13:37:00.311Z" }, + { url = "https://files.pythonhosted.org/packages/85/1f/d87c85a11cb84852c975251581862681e4a0c1c3bd456c648792203f311b/fonttools-4.60.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7916a381b094db4052ac284255186aebf74c5440248b78860cb41e300036f598", size = 4921411, upload-time = "2025-12-09T13:37:02.345Z" }, + { url = "https://files.pythonhosted.org/packages/75/c0/7efad650f5ed8e317c2633133ef3c64917e7adf2e4e2940c798f5d57ec6e/fonttools-4.60.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58c8c393d5e16b15662cfc2d988491940458aa87894c662154f50c7b49440bef", size = 5063465, upload-time = "2025-12-09T13:37:04.836Z" }, + { url = "https://files.pythonhosted.org/packages/18/a8/750518c4f8cdd79393b386bc81226047ade80239e58c6c9f5dbe1fdd8ea1/fonttools-4.60.2-cp313-cp313-win32.whl", hash = "sha256:19c6e0afd8b02008caa0aa08ab896dfce5d0bcb510c49b2c499541d5cb95a963", size = 2263443, upload-time = "2025-12-09T13:37:06.762Z" }, + { url = "https://files.pythonhosted.org/packages/b8/22/026c60376f165981f80a0e90bd98a79ae3334e9d89a3d046c4d2e265c724/fonttools-4.60.2-cp313-cp313-win_amd64.whl", hash = "sha256:6a500dc59e11b2338c2dba1f8cf11a4ae8be35ec24af8b2628b8759a61457b76", size = 2313800, upload-time = "2025-12-09T13:37:08.713Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ab/7cf1f5204e1366ddf9dc5cdc2789b571feb9eebcee0e3463c3f457df5f52/fonttools-4.60.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9387c532acbe323bbf2a920f132bce3c408a609d5f9dcfc6532fbc7e37f8ccbb", size = 2841690, upload-time = "2025-12-09T13:37:10.696Z" }, + { url = "https://files.pythonhosted.org/packages/00/3c/0bf83c6f863cc8b934952567fa2bf737cfcec8fc4ffb59b3f93820095f89/fonttools-4.60.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6f1c824185b5b8fb681297f315f26ae55abb0d560c2579242feea8236b1cfef", size = 2392191, upload-time = "2025-12-09T13:37:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/00/f0/40090d148b8907fbea12e9bdf1ff149f30cdf1769e3b2c3e0dbf5106b88d/fonttools-4.60.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:55a3129d1e4030b1a30260f1b32fe76781b585fb2111d04a988e141c09eb6403", size = 4873503, upload-time = "2025-12-09T13:37:15.142Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e0/d8b13f99e58b8c293781288ba62fe634f1f0697c9c4c0ae104d3215f3a10/fonttools-4.60.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b196e63753abc33b3b97a6fd6de4b7c4fef5552c0a5ba5e562be214d1e9668e0", size = 4968493, upload-time = "2025-12-09T13:37:18.272Z" }, + { url = "https://files.pythonhosted.org/packages/46/c5/960764d12c92bc225f02401d3067048cb7b282293d9e48e39fe2b0ec38a9/fonttools-4.60.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de76c8d740fb55745f3b154f0470c56db92ae3be27af8ad6c2e88f1458260c9a", size = 4920015, upload-time = "2025-12-09T13:37:20.334Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ab/839d8caf253d1eef3653ef4d34427d0326d17a53efaec9eb04056b670fff/fonttools-4.60.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ba6303225c95998c9fda2d410aa792c3d2c1390a09df58d194b03e17583fa25", size = 5031165, upload-time = "2025-12-09T13:37:23.57Z" }, + { url = "https://files.pythonhosted.org/packages/de/bf/3bc862796a6841cbe0725bb5512d272239b809dba631a4b0301df885e62d/fonttools-4.60.2-cp314-cp314-win32.whl", hash = "sha256:0a89728ce10d7c816fedaa5380c06d2793e7a8a634d7ce16810e536c22047384", size = 2267526, upload-time = "2025-12-09T13:37:25.821Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/c1909cacf00c76dc37b4743451561fbaaf7db4172c22a6d9394081d114c3/fonttools-4.60.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa8446e6ab8bd778b82cb1077058a2addba86f30de27ab9cc18ed32b34bc8667", size = 2319096, upload-time = "2025-12-09T13:37:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/29/b3/f66e71433f08e3a931b2b31a665aeed17fcc5e6911fc73529c70a232e421/fonttools-4.60.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4063bc81ac5a4137642865cb63dd270e37b3cd1f55a07c0d6e41d072699ccca2", size = 2925167, upload-time = "2025-12-09T13:37:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/2e/13/eeb491ff743594bbd0bee6e49422c03a59fe9c49002d3cc60eeb77414285/fonttools-4.60.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebfdb66fa69732ed604ab8e2a0431e6deff35e933a11d73418cbc7823d03b8e1", size = 2430923, upload-time = "2025-12-09T13:37:32.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/db609f785e460796e53c4dbc3874a5f4948477f27beceb5e2d24b2537666/fonttools-4.60.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50b10b3b1a72d1d54c61b0e59239e1a94c0958f4a06a1febf97ce75388dd91a4", size = 4877729, upload-time = "2025-12-09T13:37:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/85e4484dd4bfb03fee7bd370d65888cccbd3dee2681ee48c869dd5ccb23f/fonttools-4.60.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:beae16891a13b4a2ddec9b39b4de76092a3025e4d1c82362e3042b62295d5e4d", size = 5096003, upload-time = "2025-12-09T13:37:37.862Z" }, + { url = "https://files.pythonhosted.org/packages/30/49/1a98e44b71030b83d2046f981373b80571868259d98e6dae7bc20099dac6/fonttools-4.60.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:522f017fdb3766fd5d2d321774ef351cc6ce88ad4e6ac9efe643e4a2b9d528db", size = 4974410, upload-time = "2025-12-09T13:37:40.166Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/d6f775d950ee8a841012472c7303f8819423d8cc3b4530915de7265ebfa2/fonttools-4.60.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82cceceaf9c09a965a75b84a4b240dd3768e596ffb65ef53852681606fe7c9ba", size = 5002036, upload-time = "2025-12-09T13:37:42.639Z" }, + { url = "https://files.pythonhosted.org/packages/73/f6/ba6458f83ce1a9f8c3b17bd8f7b8a2205a126aac1055796b7e7cfebbd38f/fonttools-4.60.2-cp314-cp314t-win32.whl", hash = "sha256:bbfbc918a75437fe7e6d64d1b1e1f713237df1cf00f3a36dedae910b2ba01cee", size = 2330985, upload-time = "2025-12-09T13:37:45.157Z" }, + { url = "https://files.pythonhosted.org/packages/91/24/fea0ba4d3a32d4ed1103a1098bfd99dc78b5fe3bb97202920744a37b73dc/fonttools-4.60.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0e5cd9b0830f6550d58c84f3ab151a9892b50c4f9d538c5603c0ce6fff2eb3f1", size = 2396226, upload-time = "2025-12-09T13:37:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/a6d9446cb258d3fe87e311c2d7bacf8e8da3e5809fbdc3a8306db4f6b14e/fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008", size = 2857184, upload-time = "2025-12-09T13:37:49.96Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f3/1b41d0b6a8b908aa07f652111155dd653ebbf0b3385e66562556c5206685/fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc", size = 2401877, upload-time = "2025-12-09T13:37:52.307Z" }, + { url = "https://files.pythonhosted.org/packages/71/57/048fd781680c38b05c5463657d0d95d5f2391a51972176e175c01de29d42/fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311", size = 4878073, upload-time = "2025-12-09T13:37:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/363364f052a893cebd3d449588b21244a9d873620fda03ad92702d2e1bc7/fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b", size = 4835385, upload-time = "2025-12-09T13:37:58.882Z" }, + { url = "https://files.pythonhosted.org/packages/1c/38/e392bb930b2436287e6021672345db26441bf1f85f1e98f8b9784334e41d/fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e", size = 4853084, upload-time = "2025-12-09T13:38:01.578Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/0d77faeaecf7a3276a8a6dc49e2274357e6b3ed6a1774e2fdb2a7f142db0/fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5", size = 4971144, upload-time = "2025-12-09T13:38:03.748Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/6d3ac3afbcd598631bce24c3ecb919e7d0644a82fea8ddc4454312fc0be6/fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37", size = 1499411, upload-time = "2025-12-09T13:38:05.586Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/9dedf6420e23f9fa630bb97941839dddd2e1e57d1b2b85a902378dbe0bd2/fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501", size = 1547943, upload-time = "2025-12-09T13:38:07.604Z" }, + { url = "https://files.pythonhosted.org/packages/79/6c/10280af05b44fafd1dff69422805061fa1af29270bc52dce031ac69540bf/fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a", size = 1144610, upload-time = "2025-12-09T13:38:09.5Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, + { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, + { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, + { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, + { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, + { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, + { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, + { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "fsspec" +version = "2026.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp", marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "greenlet" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/f5/3e9eafb4030588337b2a2ae4df46212956854e9069c07b53aa3caabafd47/greenlet-3.2.5.tar.gz", hash = "sha256:c816554eb33e7ecf9ba4defcb1fd8c994e59be6b4110da15480b3e7447ea4286", size = 191501, upload-time = "2026-02-20T20:08:51.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/d6/b3db928fc329b1b19ba32ffe143d2305f3aaafc583f5e1074c74ec445189/greenlet-3.2.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:34cc7cf8ab6f4b85298b01e13e881265ee7b3c1daf6bc10a2944abc15d4f87c3", size = 275803, upload-time = "2026-02-20T20:06:42.541Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/ab0ad4ff3d9e1faa266de4f6c79763b33fccd9265995f2940192494cc0ec/greenlet-3.2.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c11fe0cfb0ce33132f0b5d27eeadd1954976a82e5e9b60909ec2c4b884a55382", size = 633556, upload-time = "2026-02-20T20:30:41.594Z" }, + { url = "https://files.pythonhosted.org/packages/da/dd/7b3ac77099a1671af8077ecedb12c9a1be1310e4c35bb69fd34c18ab6093/greenlet-3.2.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a145f4b1c4ed7a2c94561b7f18b4beec3d3fb6f0580db22f7ed1d544e0620b34", size = 644943, upload-time = "2026-02-20T20:37:23.084Z" }, + { url = "https://files.pythonhosted.org/packages/56/f0/bea7e7909ea9045b0c5055dad1ec9b81c82b761b4567e625f4f8349acfa1/greenlet-3.2.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:edbf4ab9a7057ee430a678fe2ef37ea5d69125d6bdc7feb42ed8d871c737e63b", size = 640849, upload-time = "2026-02-20T20:43:57.305Z" }, + { url = "https://files.pythonhosted.org/packages/0f/36/84630e9ff1dfc8b7690957c0f77834a84eabdbd9c4977c3a2d0cbd5325c2/greenlet-3.2.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1d01bdd67db3e5711e6246e451d7a0f75fae7bbf40adde129296a7f9aa7cc9", size = 639841, upload-time = "2026-02-20T20:07:17.473Z" }, + { url = "https://files.pythonhosted.org/packages/12/c4/6a2ee6c676dea7a05a3c3c1291fbc8ea44f26456b0accc891471293825af/greenlet-3.2.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd593db7ee1fa8a513a48a404f8cc4126998a48025e3f5cbbc68d51be0a6bf66", size = 588813, upload-time = "2026-02-20T20:07:56.171Z" }, + { url = "https://files.pythonhosted.org/packages/01/c0/75e75c2c993aa850292561ec80f5c263e3924e5843aa95a38716df69304c/greenlet-3.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac8db07bced2c39b987bba13a3195f8157b0cfbce54488f86919321444a1cc3c", size = 1117377, upload-time = "2026-02-20T20:32:48.452Z" }, + { url = "https://files.pythonhosted.org/packages/ee/03/e38ebf9024a0873fe8f60f5b7bc36bfb3be5e13efe4d798240f2d1f0fb73/greenlet-3.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4544ab2cfd5912e42458b13516429e029f87d8bbcdc8d5506db772941ae12493", size = 1141246, upload-time = "2026-02-20T20:06:23.576Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7b/c6e1192c795c0c12871e199237909a6bd35757d92c8472c7c019959b8637/greenlet-3.2.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acabf468466d18017e2ae5fbf1a5a88b86b48983e550e1ae1437b69a83d9f4ac", size = 276916, upload-time = "2026-02-20T20:06:18.166Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b6/9887b559f3e1952d23052ec352e9977e808a2246c7cb8282a38337221e88/greenlet-3.2.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:472841de62d60f2cafd60edd4fd4dd7253eb70e6eaf14b8990dcaf177f4af957", size = 636107, upload-time = "2026-02-20T20:30:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/8a/be/e3e48b63bbc27d660fa1d98aecb64906b90a12e686a436169c1330ef34b2/greenlet-3.2.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d951e7d628a6e8b68af469f0fe4f100ef64c4054abeb9cdafbfaa30a920c950", size = 648240, upload-time = "2026-02-20T20:37:24.608Z" }, + { url = "https://files.pythonhosted.org/packages/17/f6/2cbe999683f759f14f598234f04ae8ba6f22953a624b3a7a630003e6bfff/greenlet-3.2.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:87b791dd0e031a574249af717ac36f7031b18c35329561c1e0368201c18caf1f", size = 644170, upload-time = "2026-02-20T20:43:59.002Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ac/e731ed62576e91e533b36d0d97325adc2786674ab9e48ed8a6a24f4ef4e9/greenlet-3.2.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8317d732e2ae0935d9ed2af2ea876fa714cf6f3b887a31ca150b54329b0a6e9", size = 643313, upload-time = "2026-02-20T20:07:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/64/99e5cdceb494bd4c1341c45b93f322601d2c8a5e1e4d1c7a2d24c5ed0570/greenlet-3.2.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce8aed6fdd5e07d3cbb988cbdc188266a4eb9e1a52db9ef5c6526e59962d3933", size = 591295, upload-time = "2026-02-20T20:07:57.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e9/968e11f388c2b8792d3b8b40a57984c894a3b4745dae3662dce722653bc5/greenlet-3.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60c06b502d56d5451f60ca665691da29f79ed95e247bcf8ce5024d7bbe64acb9", size = 1120277, upload-time = "2026-02-20T20:32:50.103Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2c/b5f2c4c68d753dce08218dc5a6b21d82238fdfdc44309032f6fe24d285e6/greenlet-3.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d2a78e6f1bf3f1672df91e212a2f8314e1e7c922f065d14cbad4bc815059467", size = 1145746, upload-time = "2026-02-20T20:06:26.296Z" }, + { url = "https://files.pythonhosted.org/packages/ad/32/022b21523eee713e7550162d5ca6aed23f913cc2c6232b154b9fd9badc07/greenlet-3.2.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2acb30e77042f747ca81f0a10cc153296567e92e666c5e1b117f4595afd43352", size = 278412, upload-time = "2026-02-20T20:03:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/8a3b0ed3cc34d8b988a44349437dfa0941f9c23ac108175f7b4ccea97111/greenlet-3.2.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:393c03c26c865f17f31d8db2f09603fadbe0581ad85a5d5908b131549fc38217", size = 644616, upload-time = "2026-02-20T20:30:44.823Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/2627bea183554695016af6cae93d7474fa90f61e5a6601a84ae7841cb720/greenlet-3.2.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:04e6a202cde56043fd355fefd1552c4caa5c087528121871d950eb4f1b51fa99", size = 658813, upload-time = "2026-02-20T20:37:26.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/c6/a80fc96f7cca7962dd972875d12c52dfabc94cb02bfeb19f3e7e169fca44/greenlet-3.2.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d5583b2ffa677578a384337ee13125bdf9a427485d689014b39d638a4f3d8dbe", size = 653512, upload-time = "2026-02-20T20:44:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/75a5aeff487a26ba427a3837da6372f1fe6f2a9c6b2898e28ac99d491c11/greenlet-3.2.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45fcea7b697b91290b36eafc12fff479aca6ba6500d98ef6f34d5634c7119cbe", size = 655426, upload-time = "2026-02-20T20:07:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/53/91/9b5dfb4f3c88f8247c7a8f4c3759f0740bfa6bb0c59a9f6bf938e913df56/greenlet-3.2.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96e2bb8a56b7e1aed1dbfbbe0050cb2ecca99c7c91892fd1771e3afab63b3e3", size = 611138, upload-time = "2026-02-20T20:07:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8d/d0b086410512d9859c84e9242a9b341de9f5566011ddf3a3f6886b842b61/greenlet-3.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d7456e67b0be653dfe643bb37d9566cd30939c80f858e2ce6d2d54951f75b14a", size = 1126896, upload-time = "2026-02-20T20:32:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/ef/37/59fe12fe456e84ced6ba71781e28cde52a3124d1dd2077bc1727021f49fd/greenlet-3.2.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5ceb29d1f74c7280befbbfa27b9bf91ba4a07a1a00b2179a5d953fc219b16c42", size = 1154779, upload-time = "2026-02-20T20:06:27.583Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/d5d332fb73affaf7a1fbe80e49c2c7eae4f17c645af24a3b3fa25736d6f0/greenlet-3.2.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f2cc88b50b9006b324c1b9f5f3552f9d4564c78af57cdfb4c7baf4f0aa089146", size = 277166, upload-time = "2026-02-20T20:03:57.077Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/89458e20db5a4f1c64f9a0191561227e76d809941ca2d7529006d17d3450/greenlet-3.2.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e66872daffa360b2537170b73ad530f14fa31785b1bc78080125d92edf0a6def", size = 644674, upload-time = "2026-02-20T20:30:46.118Z" }, + { url = "https://files.pythonhosted.org/packages/90/f8/9962175d2f2eaa629a7fd7545abacc8c4deda3baa4e52c1526d2eb5f5546/greenlet-3.2.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c5445ddb7b586d870dad32ca9fc47c287d6022a528d194efdb8912093c5303ad", size = 658834, upload-time = "2026-02-20T20:37:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/81/71/52c21a7106ce5218aa6fa59ec32825b2655f875a09b69f68bd3e5d01feb3/greenlet-3.2.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd904626b8779810062cb455514594776e3cba3b8c0ba4939894df9f7b384971", size = 653091, upload-time = "2026-02-20T20:44:01.927Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d7/826d0e080f0a7ad5ec47c8d143bbd3ca0887657bb806595fe2434d12938a/greenlet-3.2.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:752c896a8c976548faafe8a306d446c6a4c68d4fd24699b84d4393bd9ac69a8e", size = 655760, upload-time = "2026-02-20T20:07:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/33bd4c2f816be8c8e16f71740c4130adf3a66a3dd2ba29de72b9d8dd1096/greenlet-3.2.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499b809e7738c8af0ff9ac9d5dd821cb93f4293065a9237543217f0b252f950a", size = 614132, upload-time = "2026-02-20T20:08:00.351Z" }, + { url = "https://files.pythonhosted.org/packages/48/79/f3891dcfc59097474a53cc3c624f2f2465e431ab493bda043b8c873fb20a/greenlet-3.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2c7429f6e9cea7cbf2637d86d3db12806ba970f7f972fcab39d6b54b4457cbaf", size = 1125286, upload-time = "2026-02-20T20:32:54.032Z" }, + { url = "https://files.pythonhosted.org/packages/ca/47/212b47e6d2d7a04c4083db1af2fdd291bc8fe99b7e3571bfa560b65fc361/greenlet-3.2.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a5e4b25e855800fba17713020c5c33e0a4b7a1829027719344f0c7c8870092a2", size = 1152825, upload-time = "2026-02-20T20:06:29Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9d/4e9b941be05f8da7ba804c6413761d2c11cca05994cbf0a015bd729419f0/greenlet-3.2.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7123b29e6bad2f3f89681be4ef316480fca798ebe8d22fbaced9cc3775007a4f", size = 277627, upload-time = "2026-02-20T20:06:04.798Z" }, + { url = "https://files.pythonhosted.org/packages/23/cb/a73625c9a35138330014ecf3740c0d62e0c2b5e7279bb7f2586b1b199fac/greenlet-3.2.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e8fe0c72603201a86b2e038daf9b6c8570715f8779566419cff543b6ace88de", size = 690001, upload-time = "2026-02-20T20:30:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/83/49/6d1531109507bce7dfb23acf57a87013627ed3ac058851176e443a6a9134/greenlet-3.2.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:050703a60603db0e817364d69e048c70af299040c13a7e67792b9e62d4571196", size = 702953, upload-time = "2026-02-20T20:37:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/90/ac/6d8fff3b273fc60ad4b46f8411fe91c1e4cca064dfff68d096bc982fa6d0/greenlet-3.2.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:04633da773ae432649a3f092a8e4add390732cc9e1ab52c8ff2c91b8dc86f202", size = 698353, upload-time = "2026-02-20T20:44:03.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/38/f958ee90fab93529b30cc1e4a59b27c1112b640570043a84af84da3b3b98/greenlet-3.2.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6712bfd520530eb67331813f7112d3ee18e206f48b3d026d8a96cd2d2ad20251", size = 698995, upload-time = "2026-02-20T20:07:22.663Z" }, + { url = "https://files.pythonhosted.org/packages/51/c1/a603906e79716d61f08afedaf8aed62017661457aef233d62d6e57ecd511/greenlet-3.2.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc06a78fa3ffbe2a75f1ebc7e040eacf6fa1050a9432953ab111fbbbf0d03c1", size = 661175, upload-time = "2026-02-20T20:08:01.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8f/f880ff4587d236b4d06893fb34da6b299aa0d00f6c8259673f80e1b6d63c/greenlet-3.2.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:dbe0e81e24982bb45907ca20152b31c2e3300ca352fdc4acbd4956e4a2cbc195", size = 274946, upload-time = "2026-02-20T20:05:21.979Z" }, + { url = "https://files.pythonhosted.org/packages/3c/50/f6c78b8420187fdfe97fcf2e6d1dd243a7742d272c32fd4d4b1095474b37/greenlet-3.2.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15871afc0d78ec87d15d8412b337f287fc69f8f669346e391585824970931c48", size = 631781, upload-time = "2026-02-20T20:30:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/26/d6/3277f92e1961e6e9f41d9f173ea74b5c1f7065072637669f761626f26cc0/greenlet-3.2.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5bf0d7d62e356ef2e87e55e46a4e930ac165f9372760fb983b5631bb479e9d3a", size = 643740, upload-time = "2026-02-20T20:37:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/c37b87659378759f158dbe03eaeb7ed002a8968f1c649b2972f5323f99b2/greenlet-3.2.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:e3f03ddd7142c758ab41c18089a1407b9959bd276b4e6dfbd8fd06403832c87a", size = 639098, upload-time = "2026-02-20T20:44:07.287Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6a/4f79d2e7b5ef3723fc5ffea0d6cb22627e5f95e0f19c973fa12bf1cf7891/greenlet-3.2.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dff6433742073e5b6ad40953a78a0e8cddcb3f6869e5ea635d29a810ca5e7d0", size = 638382, upload-time = "2026-02-20T20:07:23.883Z" }, + { url = "https://files.pythonhosted.org/packages/4d/59/7aadf33f23c65dbf4db27e7f5b60c414797a61e954352ae4a86c5c8b0553/greenlet-3.2.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdd67619cefe1cc9fcab57c8853d2bb36eca9f166c0058cc0d428d471f7c785c", size = 587516, upload-time = "2026-02-20T20:08:02.841Z" }, + { url = "https://files.pythonhosted.org/packages/1d/46/b3422959f830de28a4eea447414e6bd7b980d755892f66ab52ad805da1c4/greenlet-3.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3828b309dfb1f117fe54867512a8265d8d4f00f8de6908eef9b885f4d8789062", size = 1115818, upload-time = "2026-02-20T20:32:55.786Z" }, + { url = "https://files.pythonhosted.org/packages/54/4a/3d1c9728f093415637cf3696909fa10852632e33e68238fb8ca60eb90de1/greenlet-3.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:67725ae9fea62c95cf1aa230f1b8d4dc38f7cd14f6103d1df8a5a95657eb8e54", size = 1140219, upload-time = "2026-02-20T20:06:30.334Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d", size = 277747, upload-time = "2026-02-20T20:16:21.325Z" }, + { url = "https://files.pythonhosted.org/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13", size = 579202, upload-time = "2026-02-20T20:47:28.955Z" }, + { url = "https://files.pythonhosted.org/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e", size = 590620, upload-time = "2026-02-20T20:55:55.581Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/6e2a7d80c353587751ef3d44bb947f0565ec008a2e0927821c007e96d3a7/greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7", size = 602132, upload-time = "2026-02-20T21:02:43.261Z" }, + { url = "https://files.pythonhosted.org/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f", size = 591729, upload-time = "2026-02-20T20:20:58.395Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef", size = 1551946, upload-time = "2026-02-20T20:49:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca", size = 1618494, upload-time = "2026-02-20T20:21:06.541Z" }, + { url = "https://files.pythonhosted.org/packages/ac/78/f93e840cbaef8becaf6adafbaf1319682a6c2d8c1c20224267a5c6c8c891/greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f", size = 230092, upload-time = "2026-02-20T20:17:09.379Z" }, + { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, + { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, + { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/cb/9bb543bd987ffa1ee48202cc96a756951b734b79a542335c566148ade36c/hf_xet-1.3.2.tar.gz", hash = "sha256:e130ee08984783d12717444e538587fa2119385e5bd8fc2bb9f930419b73a7af", size = 643646, upload-time = "2026-02-27T17:26:08.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/75/462285971954269432aad2e7938c5c7ff9ec7d60129cec542ab37121e3d6/hf_xet-1.3.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:335a8f36c55fd35a92d0062f4e9201b4015057e62747b7e7001ffb203c0ee1d2", size = 3761019, upload-time = "2026-02-27T17:25:49.441Z" }, + { url = "https://files.pythonhosted.org/packages/35/56/987b0537ddaf88e17192ea09afa8eca853e55f39a4721578be436f8409df/hf_xet-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c1ae4d3a716afc774e66922f3cac8206bfa707db13f6a7e62dfff74bfc95c9a8", size = 3521565, upload-time = "2026-02-27T17:25:47.469Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5c/7e4a33a3d689f77761156cc34558047569e54af92e4d15a8f493229f6767/hf_xet-1.3.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6dbdf231efac0b9b39adcf12a07f0c030498f9212a18e8c50224d0e84ab803d", size = 4176494, upload-time = "2026-02-27T17:25:40.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b3/71e856bf9d9a69b3931837e8bf22e095775f268c8edcd4a9e8c355f92484/hf_xet-1.3.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c1980abfb68ecf6c1c7983379ed7b1e2b49a1aaf1a5aca9acc7d48e5e2e0a961", size = 3955601, upload-time = "2026-02-27T17:25:38.376Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/aecf97b3f0a981600a67ff4db15e2d433389d698a284bb0ea5d8fcdd6f7f/hf_xet-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1c88fbd90ad0d27c46b77a445f0a436ebaa94e14965c581123b68b1c52f5fd30", size = 4154770, upload-time = "2026-02-27T17:25:56.756Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e1/3af961f71a40e09bf5ee909842127b6b00f5ab4ee3817599dc0771b79893/hf_xet-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:35b855024ca37f2dd113ac1c08993e997fbe167b9d61f9ef66d3d4f84015e508", size = 4394161, upload-time = "2026-02-27T17:25:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c3/859509bade9178e21b8b1db867b8e10e9f817ab9ac1de77cb9f461ced765/hf_xet-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:31612ba0629046e425ba50375685a2586e11fb9144270ebabd75878c3eaf6378", size = 3637377, upload-time = "2026-02-27T17:26:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/724cfbef4da92d577b71f68bf832961c8919f36c60d28d289a9fc9d024d4/hf_xet-1.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:433c77c9f4e132b562f37d66c9b22c05b5479f243a1f06a120c1c06ce8b1502a", size = 3497875, upload-time = "2026-02-27T17:26:09.034Z" }, + { url = "https://files.pythonhosted.org/packages/ba/75/9d54c1ae1d05fb704f977eca1671747babf1957f19f38ae75c5933bc2dc1/hf_xet-1.3.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:c34e2c7aefad15792d57067c1c89b2b02c1bbaeabd7f8456ae3d07b4bbaf4094", size = 3761076, upload-time = "2026-02-27T17:25:55.42Z" }, + { url = "https://files.pythonhosted.org/packages/f2/8a/08a24b6c6f52b5d26848c16e4b6d790bb810d1bf62c3505bed179f7032d3/hf_xet-1.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4bc995d6c41992831f762096020dc14a65fdf3963f86ffed580b596d04de32e3", size = 3521745, upload-time = "2026-02-27T17:25:54.217Z" }, + { url = "https://files.pythonhosted.org/packages/b5/db/a75cf400dd8a1a8acf226a12955ff6ee999f272dfc0505bafd8079a61267/hf_xet-1.3.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:959083c89dee30f7d6f890b36cdadda823386c4de63b1a30384a75bfd2ae995d", size = 4176301, upload-time = "2026-02-27T17:25:46.044Z" }, + { url = "https://files.pythonhosted.org/packages/01/40/6c4c798ffdd83e740dd3925c4e47793b07442a9efa3bc3866ba141a82365/hf_xet-1.3.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cfa760888633b08c01b398d212ce7e8c0d7adac6c86e4b20dfb2397d8acd78ee", size = 3955437, upload-time = "2026-02-27T17:25:44.703Z" }, + { url = "https://files.pythonhosted.org/packages/0c/09/9a3aa7c5f07d3e5cc57bb750d12a124ffa72c273a87164bd848f9ac5cc14/hf_xet-1.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3155a02e083aa21fd733a7485c7c36025e49d5975c8d6bda0453d224dd0b0ac4", size = 4154535, upload-time = "2026-02-27T17:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e0/831f7fa6d90cb47a230bc23284b502c700e1483bbe459437b3844cdc0776/hf_xet-1.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91b1dc03c31cbf733d35dc03df7c5353686233d86af045e716f1e0ea4a2673cf", size = 4393891, upload-time = "2026-02-27T17:26:06.607Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/6ed472fdce7f8b70f5da6e3f05be76816a610063003bfd6d9cea0bbb58a3/hf_xet-1.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:211f30098512d95e85ad03ae63bd7dd2c4df476558a5095d09f9e38e78cbf674", size = 3637583, upload-time = "2026-02-27T17:26:17.349Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/a069edc4570b3f8e123c0b80fadc94530f3d7b01394e1fc1bb223339366c/hf_xet-1.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4a6817c41de7c48ed9270da0b02849347e089c5ece9a0e72ae4f4b3a57617f82", size = 3497977, upload-time = "2026-02-27T17:26:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/d8/28/dbb024e2e3907f6f3052847ca7d1a2f7a3972fafcd53ff79018977fcb3e4/hf_xet-1.3.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f93b7595f1d8fefddfede775c18b5c9256757824f7f6832930b49858483cd56f", size = 3763961, upload-time = "2026-02-27T17:25:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/e4/71/b99aed3823c9d1795e4865cf437d651097356a3f38c7d5877e4ac544b8e4/hf_xet-1.3.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a85d3d43743174393afe27835bde0cd146e652b5fcfdbcd624602daef2ef3259", size = 3526171, upload-time = "2026-02-27T17:25:50.968Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/907890ce6ef5598b5920514f255ed0a65f558f820515b18db75a51b2f878/hf_xet-1.3.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7c2a054a97c44e136b1f7f5a78f12b3efffdf2eed3abc6746fc5ea4b39511633", size = 4180750, upload-time = "2026-02-27T17:25:43.125Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ad/bc7f41f87173d51d0bce497b171c4ee0cbde1eed2d7b4216db5d0ada9f50/hf_xet-1.3.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:06b724a361f670ae557836e57801b82c75b534812e351a87a2c739f77d1e0635", size = 3961035, upload-time = "2026-02-27T17:25:41.837Z" }, + { url = "https://files.pythonhosted.org/packages/73/38/600f4dda40c4a33133404d9fe644f1d35ff2d9babb4d0435c646c63dd107/hf_xet-1.3.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:305f5489d7241a47e0458ef49334be02411d1d0f480846363c1c8084ed9916f7", size = 4161378, upload-time = "2026-02-27T17:26:00.365Z" }, + { url = "https://files.pythonhosted.org/packages/00/b3/7bc1ff91d1ac18420b7ad1e169b618b27c00001b96310a89f8a9294fe509/hf_xet-1.3.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:06cdbde243c85f39a63b28e9034321399c507bcd5e7befdd17ed2ccc06dfe14e", size = 4398020, upload-time = "2026-02-27T17:26:03.977Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/99bfd948a3ed3620ab709276df3ad3710dcea61976918cce8706502927af/hf_xet-1.3.2-cp37-abi3-win_amd64.whl", hash = "sha256:9298b47cce6037b7045ae41482e703c471ce36b52e73e49f71226d2e8e5685a1", size = 3641624, upload-time = "2026-02-27T17:26:13.542Z" }, + { url = "https://files.pythonhosted.org/packages/cc/02/9a6e4ca1f3f73a164c0cd48e41b3cc56585dcc37e809250de443d673266f/hf_xet-1.3.2-cp37-abi3-win_arm64.whl", hash = "sha256:83d8ec273136171431833a6957e8f3af496bee227a0fe47c7b8b39c106d1749a", size = 3503976, upload-time = "2026-02-27T17:26:12.123Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer", version = "0.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typer", version = "0.24.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/7a/304cec37112382c4fe29a43bcb0d5891f922785d18745883d2aa4eb74e4b/huggingface_hub-1.6.0.tar.gz", hash = "sha256:d931ddad8ba8dfc1e816bf254810eb6f38e5c32f60d4184b5885662a3b167325", size = 717071, upload-time = "2026-03-06T14:19:18.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e3/e3a44f54c8e2f28983fcf07f13d4260b37bd6a0d3a081041bc60b91d230e/huggingface_hub-1.6.0-py3-none-any.whl", hash = "sha256:ef40e2d5cb85e48b2c067020fa5142168342d5108a1b267478ed384ecbf18961", size = 612874, upload-time = "2026-03-06T14:19:16.844Z" }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "identify" +version = "2.6.17" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipykernel" +version = "6.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "appnope", marker = "python_full_version < '3.10' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version < '3.10'" }, + { name = "debugpy", marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "psutil", marker = "python_full_version < '3.10'" }, + { name = "pyzmq", marker = "python_full_version < '3.10'" }, + { name = "tornado", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493, upload-time = "2025-10-20T11:42:39.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003, upload-time = "2025-10-20T11:42:37.502Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "appnope", marker = "python_full_version >= '3.10' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version >= '3.10'" }, + { name = "debugpy", marker = "python_full_version >= '3.10'" }, + { name = "ipython", version = "8.38.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "jupyter-client", version = "8.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "psutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, +] + +[[package]] +name = "ipython" +version = "8.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "jedi", marker = "python_full_version < '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, + { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "stack-data", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" }, +] + +[[package]] +name = "ipython" +version = "8.38.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.10.*'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "jedi", marker = "python_full_version == '3.10.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, + { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, + { name = "stack-data", marker = "python_full_version == '3.10.*'" }, + { name = "traitlets", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/61/1810830e8b93c72dcd3c0f150c80a00c3deb229562d9423807ec92c3a539/ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39", size = 5513996, upload-time = "2026-01-05T10:59:06.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/df/db59624f4c71b39717c423409950ac3f2c8b2ce4b0aac843112c7fb3f721/ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86", size = 831813, upload-time = "2026-01-05T10:59:04.239Z" }, +] + +[[package]] +name = "ipython" +version = "9.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.11.*'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version == '3.11.*'" }, + { name = "jedi", marker = "python_full_version == '3.11.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.11.*'" }, + { name = "pexpect", marker = "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "stack-data", marker = "python_full_version == '3.11.*'" }, + { name = "traitlets", marker = "python_full_version == '3.11.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, +] + +[[package]] +name = "ipython" +version = "9.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, + { name = "jedi", marker = "python_full_version >= '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, + { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "stack-data", marker = "python_full_version >= '3.12'" }, + { name = "traitlets", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/2f/0b98b06a345a761bec91a079ccae392d282690c2d8272e708f4d10829e22/Jinja2-2.8.tar.gz", hash = "sha256:bc1ff2ff88dbfacefde4ddde471d1417d3b304e8df103a7a9437d47269201bf4", size = 357055, upload-time = "2015-07-26T17:49:47.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/a1/c56bc4d99dc2663514a8481511e80eba8994133ae75eebdadfc91a5597d9/Jinja2-2.8-py2.py3-none-any.whl", hash = "sha256:1cc03ef32b64be19e0a5b54578dd790906a34943fe9102cfdae0d4495bd536b4", size = 263888, upload-time = "2015-07-26T17:52:06.373Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, + { name = "pyzmq", marker = "python_full_version < '3.10'" }, + { name = "tornado", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pywin32", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "platformdirs", version = "4.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286, upload-time = "2024-09-04T09:39:44.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440, upload-time = "2024-09-04T09:03:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758, upload-time = "2024-09-04T09:03:46.582Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311, upload-time = "2024-09-04T09:03:47.973Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109, upload-time = "2024-09-04T09:03:49.281Z" }, + { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814, upload-time = "2024-09-04T09:03:51.444Z" }, + { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881, upload-time = "2024-09-04T09:03:53.357Z" }, + { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972, upload-time = "2024-09-04T09:03:55.082Z" }, + { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787, upload-time = "2024-09-04T09:03:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212, upload-time = "2024-09-04T09:03:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399, upload-time = "2024-09-04T09:04:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688, upload-time = "2024-09-04T09:04:02.216Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493, upload-time = "2024-09-04T09:04:04.571Z" }, + { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191, upload-time = "2024-09-04T09:04:05.969Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644, upload-time = "2024-09-04T09:04:07.408Z" }, + { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877, upload-time = "2024-09-04T09:04:08.869Z" }, + { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347, upload-time = "2024-09-04T09:04:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442, upload-time = "2024-09-04T09:04:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762, upload-time = "2024-09-04T09:04:12.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319, upload-time = "2024-09-04T09:04:13.635Z" }, + { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260, upload-time = "2024-09-04T09:04:14.878Z" }, + { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589, upload-time = "2024-09-04T09:04:16.514Z" }, + { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080, upload-time = "2024-09-04T09:04:18.322Z" }, + { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049, upload-time = "2024-09-04T09:04:20.266Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376, upload-time = "2024-09-04T09:04:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231, upload-time = "2024-09-04T09:04:24.526Z" }, + { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634, upload-time = "2024-09-04T09:04:25.899Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024, upload-time = "2024-09-04T09:04:28.523Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484, upload-time = "2024-09-04T09:04:30.547Z" }, + { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078, upload-time = "2024-09-04T09:04:33.218Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645, upload-time = "2024-09-04T09:04:34.371Z" }, + { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022, upload-time = "2024-09-04T09:04:35.786Z" }, + { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536, upload-time = "2024-09-04T09:04:37.525Z" }, + { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808, upload-time = "2024-09-04T09:04:38.637Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531, upload-time = "2024-09-04T09:04:39.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894, upload-time = "2024-09-04T09:04:41.6Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296, upload-time = "2024-09-04T09:04:42.886Z" }, + { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450, upload-time = "2024-09-04T09:04:46.284Z" }, + { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168, upload-time = "2024-09-04T09:04:47.91Z" }, + { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308, upload-time = "2024-09-04T09:04:49.465Z" }, + { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186, upload-time = "2024-09-04T09:04:50.949Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877, upload-time = "2024-09-04T09:04:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204, upload-time = "2024-09-04T09:04:54.385Z" }, + { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461, upload-time = "2024-09-04T09:04:56.307Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358, upload-time = "2024-09-04T09:04:57.922Z" }, + { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119, upload-time = "2024-09-04T09:04:59.332Z" }, + { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367, upload-time = "2024-09-04T09:05:00.804Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884, upload-time = "2024-09-04T09:05:01.924Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528, upload-time = "2024-09-04T09:05:02.983Z" }, + { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913, upload-time = "2024-09-04T09:05:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627, upload-time = "2024-09-04T09:05:05.119Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888, upload-time = "2024-09-04T09:05:06.191Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145, upload-time = "2024-09-04T09:05:07.919Z" }, + { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448, upload-time = "2024-09-04T09:05:10.01Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750, upload-time = "2024-09-04T09:05:11.598Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175, upload-time = "2024-09-04T09:05:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963, upload-time = "2024-09-04T09:05:15.925Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220, upload-time = "2024-09-04T09:05:17.434Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463, upload-time = "2024-09-04T09:05:18.997Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842, upload-time = "2024-09-04T09:05:21.299Z" }, + { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635, upload-time = "2024-09-04T09:05:23.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556, upload-time = "2024-09-04T09:05:25.907Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364, upload-time = "2024-09-04T09:05:27.184Z" }, + { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887, upload-time = "2024-09-04T09:05:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530, upload-time = "2024-09-04T09:05:30.225Z" }, + { url = "https://files.pythonhosted.org/packages/11/88/37ea0ea64512997b13d69772db8dcdc3bfca5442cda3a5e4bb943652ee3e/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd", size = 122449, upload-time = "2024-09-04T09:05:55.311Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/5a5c46078362cb3882dcacad687c503089263c017ca1241e0483857791eb/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583", size = 65757, upload-time = "2024-09-04T09:05:56.906Z" }, + { url = "https://files.pythonhosted.org/packages/8a/be/a6ae58978772f685d48dd2e84460937761c53c4bbd84e42b0336473d9775/kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417", size = 64312, upload-time = "2024-09-04T09:05:58.384Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/18ef6f452d311e1e1eb180c9bf5589187fa1f042db877e6fe443ef10099c/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904", size = 1626966, upload-time = "2024-09-04T09:05:59.855Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/40655f6c3fa11ce740e8a964fa8e4c0479c87d6a7944b95af799c7a55dfe/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a", size = 1607044, upload-time = "2024-09-04T09:06:02.16Z" }, + { url = "https://files.pythonhosted.org/packages/fd/93/af67dbcfb9b3323bbd2c2db1385a7139d8f77630e4a37bb945b57188eb2d/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8", size = 1391879, upload-time = "2024-09-04T09:06:03.908Z" }, + { url = "https://files.pythonhosted.org/packages/40/6f/d60770ef98e77b365d96061d090c0cd9e23418121c55fff188fa4bdf0b54/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2", size = 1504751, upload-time = "2024-09-04T09:06:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/fa/3a/5f38667d313e983c432f3fcd86932177519ed8790c724e07d77d1de0188a/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88", size = 1436990, upload-time = "2024-09-04T09:06:08.126Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/1520301a47326e6a6043b502647e42892be33b3f051e9791cc8bb43f1a32/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde", size = 2191122, upload-time = "2024-09-04T09:06:10.345Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c4/eb52da300c166239a2233f1f9c4a1b767dfab98fae27681bfb7ea4873cb6/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c", size = 2338126, upload-time = "2024-09-04T09:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/1a/cb/42b92fd5eadd708dd9107c089e817945500685f3437ce1fd387efebc6d6e/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2", size = 2298313, upload-time = "2024-09-04T09:06:14.562Z" }, + { url = "https://files.pythonhosted.org/packages/4f/eb/be25aa791fe5fc75a8b1e0c965e00f942496bc04635c9aae8035f6b76dcd/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb", size = 2437784, upload-time = "2024-09-04T09:06:16.767Z" }, + { url = "https://files.pythonhosted.org/packages/c5/22/30a66be7f3368d76ff95689e1c2e28d382383952964ab15330a15d8bfd03/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327", size = 2253988, upload-time = "2024-09-04T09:06:18.705Z" }, + { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980, upload-time = "2024-09-04T09:06:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847, upload-time = "2024-09-04T09:06:21.407Z" }, + { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494, upload-time = "2024-09-04T09:06:22.648Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491, upload-time = "2024-09-04T09:06:24.188Z" }, + { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648, upload-time = "2024-09-04T09:06:25.559Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257, upload-time = "2024-09-04T09:06:27.038Z" }, + { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906, upload-time = "2024-09-04T09:06:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951, upload-time = "2024-09-04T09:06:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715, upload-time = "2024-09-04T09:06:31.489Z" }, + { url = "https://files.pythonhosted.org/packages/d5/df/ce37d9b26f07ab90880923c94d12a6ff4d27447096b4c849bfc4339ccfdf/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39", size = 58666, upload-time = "2024-09-04T09:06:43.756Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d3/e4b04f43bc629ac8e186b77b2b1a251cdfa5b7610fa189dc0db622672ce6/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e", size = 57088, upload-time = "2024-09-04T09:06:45.406Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/752df58e2d339e670a535514d2db4fe8c842ce459776b8080fbe08ebb98e/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608", size = 84321, upload-time = "2024-09-04T09:06:47.557Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f8/fe6484e847bc6e238ec9f9828089fb2c0bb53f2f5f3a79351fde5b565e4f/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674", size = 80776, upload-time = "2024-09-04T09:06:49.235Z" }, + { url = "https://files.pythonhosted.org/packages/9b/57/d7163c0379f250ef763aba85330a19feefb5ce6cb541ade853aaba881524/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225", size = 79984, upload-time = "2024-09-04T09:06:51.336Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811, upload-time = "2024-09-04T09:06:53.078Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, + { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, + { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, + { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, + { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, + { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, + { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "kloppy" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version < '3.10'" }, + { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["http"], marker = "python_full_version >= '3.10'" }, + { name = "lxml" }, + { name = "pytz" }, + { name = "sortedcontainers" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/41/d456e664badb4f289f58ffd864a33ac55650a64000a2549b45d2da207b28/kloppy-3.18.0.tar.gz", hash = "sha256:684f03fb228290592fbc2fd180f7d428aeb21d001c5f6c6d3361a21ef92fa17b", size = 224043, upload-time = "2025-10-23T08:55:23.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/79/7da8eae36417749744e1364adaaff16749aa22b6b023c8acbe8eda30bb79/kloppy-3.18.0-py3-none-any.whl", hash = "sha256:cc5087a2e8fe32a4095ba24498b5838eac8724354b99e5695b5b05c469bb63fe", size = 294701, upload-time = "2025-10-23T08:55:21.462Z" }, +] + +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/79a14e672256ef58144a24eb49adb338ec02de67ff4b45320af6504682ab/librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a", size = 54707, upload-time = "2025-12-06T19:03:10.881Z" }, + { url = "https://files.pythonhosted.org/packages/58/fa/b709c65a9d5eab85f7bcfe0414504d9775aaad6e78727a0327e175474caa/librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec", size = 56670, upload-time = "2025-12-06T19:03:12.107Z" }, + { url = "https://files.pythonhosted.org/packages/3a/56/0685a0772ec89ddad4c00e6b584603274c3d818f9a68e2c43c4eb7b39ee9/librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a", size = 161045, upload-time = "2025-12-06T19:03:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d9/863ada0c5ce48aefb89df1555e392b2209fcb6daee4c153c031339b9a89b/librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7", size = 169532, upload-time = "2025-12-06T19:03:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/a0/71da6c8724fd16c31749905ef1c9e11de206d9301b5be984bf2682b4efb3/librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51", size = 183277, upload-time = "2025-12-06T19:03:16.446Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/9c97bf2f8338ba1914de233ea312bba2bbd7c59f43f807b3e119796bab18/librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b", size = 179045, upload-time = "2025-12-06T19:03:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/ceea067f489e904cb4ddcca3c9b06ba20229bc3fa7458711e24a5811f162/librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90", size = 173521, upload-time = "2025-12-06T19:03:19.17Z" }, + { url = "https://files.pythonhosted.org/packages/7a/41/6cb18f5da9c89ed087417abb0127a445a50ad4eaf1282ba5b52588187f47/librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71", size = 193592, upload-time = "2025-12-06T19:03:20.637Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3c/fcef208746584e7c78584b7aedc617130c4a4742cb8273361bbda8b183b5/librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c", size = 47201, upload-time = "2025-12-06T19:03:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bf/d8a6c35d1b2b789a4df9b3ddb1c8f535ea373fde2089698965a8f0d62138/librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806", size = 54371, upload-time = "2025-12-06T19:03:23.231Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, + { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, + { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, + { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, + { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, + { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, + { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, + { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, + { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/e1/70/b3f19e3bb34f44e218c8271dc0b2b14eb6b183fbccbececf94c71e2b5e69/librt-0.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd8551aa21df6c60baa2624fd086ae7486bdde00c44097b32e1d1b1966e365e0", size = 54850, upload-time = "2025-12-06T19:04:32.742Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/6599ed7726aaa9b5bacea206d5861b94e76866240e2f394a59594bf3db46/librt-0.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6eb9295c730e26b849ed1f4022735f36863eb46b14b6e10604c1c39b8b5efaea", size = 56797, upload-time = "2025-12-06T19:04:34.193Z" }, + { url = "https://files.pythonhosted.org/packages/33/83/216db13224a6f688787f456909bbc50f9d951c0f4bea8ba38a2eb931d581/librt-0.7.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3edbf257c40d21a42615e9e332a6b10a8bacaaf58250aed8552a14a70efd0d65", size = 159681, upload-time = "2025-12-06T19:04:35.554Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/0a490c8ba3bc90090647ac7b9b3c63c16af7378bcabe3ff4c7d7890d66e5/librt-0.7.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b29e97273bd6999e2bfe9fe3531b1f4f64effd28327bced048a33e49b99674a", size = 168505, upload-time = "2025-12-06T19:04:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/5e/16/b47c60805285caa06728d61d933fdd6db5b7321f375ce496cb7fdbeb1a44/librt-0.7.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e40520c37926166c24d0c2e0f3bc3a5f46646c34bdf7b4ea9747c297d6ee809", size = 182234, upload-time = "2025-12-06T19:04:37.889Z" }, + { url = "https://files.pythonhosted.org/packages/2d/2f/bef211d7f0d55fa2484d2c644b2cdae8c9c5eec050754b0516e6582ad452/librt-0.7.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6bdd9adfca615903578d2060ee8a6eb1c24eaf54919ff0ddc820118e5718931b", size = 178276, upload-time = "2025-12-06T19:04:39.408Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/5a3e7762b086b62fabb31fd4deaaf3ba888cfdd3b8f2e3247f076c18a6ff/librt-0.7.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f57aca20e637750a2c18d979f7096e2c2033cc40cf7ed201494318de1182f135", size = 172602, upload-time = "2025-12-06T19:04:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/533d5bfd5b377eb03ed54101814b530fc1f9bbe0e79971c641a3f15bfb33/librt-0.7.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cad9971881e4fec00d96af7eaf4b63aa7a595696fc221808b0d3ce7ca9743258", size = 192741, upload-time = "2025-12-06T19:04:41.738Z" }, + { url = "https://files.pythonhosted.org/packages/9f/69/0b87ce8e95f65ebc864f390f1139b8fe9fac6fb64b797307447b1719610c/librt-0.7.3-cp39-cp39-win32.whl", hash = "sha256:170cdb8436188347af17bf9cccf3249ba581c933ed56d926497119d4cf730cec", size = 47154, upload-time = "2025-12-06T19:04:42.96Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/070dee0add2d6e742be4d8b965d5a37c24562b43e8ef7deba8ed5b5d3c0f/librt-0.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:b278a9248a4e3260fee3db7613772ca9ab6763a129d6d6f29555e2f9b168216d", size = 54339, upload-time = "2025-12-06T19:04:44.415Z" }, +] + +[[package]] +name = "lxml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/8a/f8192a08237ef2fb1b19733f709db88a4c43bc8ab8357f01cb41a27e7f6a/lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388", size = 8590589, upload-time = "2025-09-22T04:00:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/27bcd07ae17ff5e5536e8d88f4c7d581b48963817a13de11f3ac3329bfa2/lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153", size = 4629671, upload-time = "2025-09-22T04:00:15.411Z" }, + { url = "https://files.pythonhosted.org/packages/02/5a/a7d53b3291c324e0b6e48f3c797be63836cc52156ddf8f33cd72aac78866/lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31", size = 4999961, upload-time = "2025-09-22T04:00:17.619Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/d465e9b89df1761674d8672bb3e4ae2c47033b01ec243964b6e334c6743f/lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9", size = 5157087, upload-time = "2025-09-22T04:00:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/38/3073cd7e3e8dfc3ba3c3a139e33bee3a82de2bfb0925714351ad3d255c13/lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8", size = 5067620, upload-time = "2025-09-22T04:00:21.877Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d3/1e001588c5e2205637b08985597827d3827dbaaece16348c8822bfe61c29/lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba", size = 5406664, upload-time = "2025-09-22T04:00:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/20/cf/cab09478699b003857ed6ebfe95e9fb9fa3d3c25f1353b905c9b73cfb624/lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c", size = 5289397, upload-time = "2025-09-22T04:00:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/02a2d0c38ac9a8b9f9e5e1bbd3f24b3f426044ad618b552e9549ee91bd63/lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c", size = 4772178, upload-time = "2025-09-22T04:00:27.602Z" }, + { url = "https://files.pythonhosted.org/packages/56/87/e1ceadcc031ec4aa605fe95476892d0b0ba3b7f8c7dcdf88fdeff59a9c86/lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321", size = 5358148, upload-time = "2025-09-22T04:00:29.323Z" }, + { url = "https://files.pythonhosted.org/packages/fe/13/5bb6cf42bb228353fd4ac5f162c6a84fd68a4d6f67c1031c8cf97e131fc6/lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1", size = 5112035, upload-time = "2025-09-22T04:00:31.061Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e2/ea0498552102e59834e297c5c6dff8d8ded3db72ed5e8aad77871476f073/lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34", size = 4799111, upload-time = "2025-09-22T04:00:33.11Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9e/8de42b52a73abb8af86c66c969b3b4c2a96567b6ac74637c037d2e3baa60/lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a", size = 5351662, upload-time = "2025-09-22T04:00:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/28/a2/de776a573dfb15114509a37351937c367530865edb10a90189d0b4b9b70a/lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c", size = 5314973, upload-time = "2025-09-22T04:00:37.086Z" }, + { url = "https://files.pythonhosted.org/packages/50/a0/3ae1b1f8964c271b5eec91db2043cf8c6c0bce101ebb2a633b51b044db6c/lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b", size = 3611953, upload-time = "2025-09-22T04:00:39.224Z" }, + { url = "https://files.pythonhosted.org/packages/d1/70/bd42491f0634aad41bdfc1e46f5cff98825fb6185688dc82baa35d509f1a/lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0", size = 4032695, upload-time = "2025-09-22T04:00:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d0/05c6a72299f54c2c561a6c6cbb2f512e047fca20ea97a05e57931f194ac4/lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5", size = 3680051, upload-time = "2025-09-22T04:00:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, + { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, + { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, + { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, + { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, + { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/dd13c74fad495957374c8a81c932f4874d3dca5aa0db9e4369f06a399718/lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694", size = 8602363, upload-time = "2025-09-22T04:03:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f4/edb9d47dce464b5dd044d35775ee794364935b93ab6226c95e199118890d/lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90", size = 4634995, upload-time = "2025-09-22T04:04:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/f2/d80c97b6ed83a99bc24b2b29919d5e618af5322df6d3aa61064093712309/lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62", size = 5003737, upload-time = "2025-09-22T04:04:02.98Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f1/18b750f79f8889b9109b24749f23ac137870b4f685edc4be54be0ff2c730/lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444", size = 5160821, upload-time = "2025-09-22T04:04:04.854Z" }, + { url = "https://files.pythonhosted.org/packages/cf/88/2b6a415dbad411c3e9c092128eb7db06054d2d9460aa56676d17ee4f4fd5/lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad", size = 5070959, upload-time = "2025-09-22T04:04:07.042Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d0/5354afaa0f2e53625e5f96f6bd049a4875c3ab79d96d6c4871dd1f4a98c4/lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48", size = 5410267, upload-time = "2025-09-22T04:04:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/51/63/10dea35a01291dc529fa9d6ba204ea627a1c77b7fbb180d404f6cc4dd2fd/lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93", size = 5292990, upload-time = "2025-09-22T04:04:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/37/58/51ef422d8bec58db600b3552e5f2d870ec01ffacf11d98689c42ffdcbf7f/lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c", size = 4776318, upload-time = "2025-09-22T04:04:14.22Z" }, + { url = "https://files.pythonhosted.org/packages/77/97/3f797820e82e3a58a19bc51068b40f3b9ab7d0934ba6e5ba6b147b618319/lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c", size = 5360191, upload-time = "2025-09-22T04:04:16.236Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/a9306a8ab122e2f5dfbf4f71fb09beeadca26b0c275708432bbc33f40edc/lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf", size = 5116114, upload-time = "2025-09-22T04:04:18.594Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/2118a1685277b9fa8726ec7ee903db55aa300dcea3d406a220cbe3710953/lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6", size = 4801704, upload-time = "2025-09-22T04:04:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e8/d5be34da2059dc9a4ff8643fd6ad3f8234a27b2a44831b7fff58c4dbb3e3/lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84", size = 5355451, upload-time = "2025-09-22T04:04:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/61/84/5aebc8e150d5bf488815ea2d8798c7ff509cc37b5725caa3c1f11bdd3245/lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb", size = 5318630, upload-time = "2025-09-22T04:04:24.301Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/629ae603c1c17fb7adc9df2bc21aa5ac96afb84001700b13c1f038f3118c/lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f", size = 3614032, upload-time = "2025-09-22T04:04:26.158Z" }, + { url = "https://files.pythonhosted.org/packages/71/de/07b7b1249acbecbf48f7e42c3ce87a657af6ff38e30f12a1ad81f16010f2/lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304", size = 4035311, upload-time = "2025-09-22T04:04:28.413Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/02c4c55b281606f3c8e118300e16a9fcf5f3462cc46ce740ed0b82fc3f1b/lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa", size = 3683462, upload-time = "2025-09-22T04:04:30.399Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9c/780c9a8fce3f04690b374f72f41306866b0400b9d0fdf3e17aaa37887eed/lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6", size = 3939264, upload-time = "2025-09-22T04:04:32.892Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/1ab260c00adf645d8bf7dec7f920f744b032f69130c681302821d5debea6/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba", size = 4216435, upload-time = "2025-09-22T04:04:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/f2/37/565f3b3d7ffede22874b6d86be1a1763d00f4ea9fc5b9b6ccb11e4ec8612/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5", size = 4325913, upload-time = "2025-09-22T04:04:37.205Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/f3a1b169b2fb9d03467e2e3c0c752ea30e993be440a068b125fc7dd248b0/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4", size = 4269357, upload-time = "2025-09-22T04:04:39.322Z" }, + { url = "https://files.pythonhosted.org/packages/77/a2/585a28fe3e67daa1cf2f06f34490d556d121c25d500b10082a7db96e3bcd/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d", size = 4412295, upload-time = "2025-09-22T04:04:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/a57dd8bcebd7c69386c20263830d4fa72d27e6b72a229ef7a48e88952d9a/lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d", size = 3516913, upload-time = "2025-09-22T04:04:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "0.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz", hash = "sha256:a4ec1aff59b95a14b45eb2e23761a0179e98319da5a7eb76b56ea8cdc7b871c3", size = 13416, upload-time = "2014-05-08T14:58:53.945Z" } + +[[package]] +name = "matplotlib" +version = "3.9.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "cycler", marker = "python_full_version < '3.10'" }, + { name = "fonttools", version = "4.60.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-resources", marker = "python_full_version < '3.10'" }, + { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyparsing", marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529, upload-time = "2024-12-13T05:56:34.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/94/27d2e2c30d54b56c7b764acc1874a909e34d1965a427fc7092bb6a588b63/matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50", size = 7885089, upload-time = "2024-12-13T05:54:24.224Z" }, + { url = "https://files.pythonhosted.org/packages/c6/25/828273307e40a68eb8e9df832b6b2aaad075864fdc1de4b1b81e40b09e48/matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff", size = 7770600, upload-time = "2024-12-13T05:54:27.214Z" }, + { url = "https://files.pythonhosted.org/packages/f2/65/f841a422ec994da5123368d76b126acf4fc02ea7459b6e37c4891b555b83/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26", size = 8200138, upload-time = "2024-12-13T05:54:29.497Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/272aca07a38804d93b6050813de41ca7ab0e29ba7a9dd098e12037c919a9/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50", size = 8312711, upload-time = "2024-12-13T05:54:34.396Z" }, + { url = "https://files.pythonhosted.org/packages/98/37/f13e23b233c526b7e27ad61be0a771894a079e0f7494a10d8d81557e0e9a/matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5", size = 9090622, upload-time = "2024-12-13T05:54:36.808Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211, upload-time = "2024-12-13T05:54:40.596Z" }, + { url = "https://files.pythonhosted.org/packages/74/4b/65be7959a8fa118a3929b49a842de5b78bb55475236fcf64f3e308ff74a0/matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c", size = 7894430, upload-time = "2024-12-13T05:54:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e9/18/80f70d91896e0a517b4a051c3fd540daa131630fd75e02e250365353b253/matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099", size = 7780045, upload-time = "2024-12-13T05:54:46.414Z" }, + { url = "https://files.pythonhosted.org/packages/a2/73/ccb381026e3238c5c25c3609ba4157b2d1a617ec98d65a8b4ee4e1e74d02/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249", size = 8209906, upload-time = "2024-12-13T05:54:49.459Z" }, + { url = "https://files.pythonhosted.org/packages/ab/33/1648da77b74741c89f5ea95cbf42a291b4b364f2660b316318811404ed97/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423", size = 8322873, upload-time = "2024-12-13T05:54:53.066Z" }, + { url = "https://files.pythonhosted.org/packages/57/d3/8447ba78bc6593c9044c372d1609f8ea10fb1e071e7a9e0747bea74fc16c/matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e", size = 9099566, upload-time = "2024-12-13T05:54:55.522Z" }, + { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065, upload-time = "2024-12-13T05:54:58.337Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2b/c918bf6c19d6445d1cefe3d2e42cb740fb997e14ab19d4daeb6a7ab8a157/matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70", size = 7891131, upload-time = "2024-12-13T05:55:02.837Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e5/b4e8fc601ca302afeeabf45f30e706a445c7979a180e3a978b78b2b681a4/matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483", size = 7776365, upload-time = "2024-12-13T05:55:05.158Z" }, + { url = "https://files.pythonhosted.org/packages/99/06/b991886c506506476e5d83625c5970c656a491b9f80161458fed94597808/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f", size = 8200707, upload-time = "2024-12-13T05:55:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e2/556b627498cb27e61026f2d1ba86a78ad1b836fef0996bef5440e8bc9559/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00", size = 8313761, upload-time = "2024-12-13T05:55:12.95Z" }, + { url = "https://files.pythonhosted.org/packages/58/ff/165af33ec766ff818306ea88e91f9f60d2a6ed543be1eb122a98acbf3b0d/matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0", size = 9095284, upload-time = "2024-12-13T05:55:16.199Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160, upload-time = "2024-12-13T05:55:19.991Z" }, + { url = "https://files.pythonhosted.org/packages/49/b1/999f89a7556d101b23a2f0b54f1b6e140d73f56804da1398f2f0bc0924bc/matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6", size = 7891499, upload-time = "2024-12-13T05:55:22.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/7b/06a32b13a684977653396a1bfcd34d4e7539c5d55c8cbfaa8ae04d47e4a9/matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45", size = 7776802, upload-time = "2024-12-13T05:55:25.947Z" }, + { url = "https://files.pythonhosted.org/packages/65/87/ac498451aff739e515891bbb92e566f3c7ef31891aaa878402a71f9b0910/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858", size = 8200802, upload-time = "2024-12-13T05:55:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6b/9eb761c00e1cb838f6c92e5f25dcda3f56a87a52f6cb8fdfa561e6cf6a13/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64", size = 8313880, upload-time = "2024-12-13T05:55:30.965Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/c8eaa600e2085eec7e38cbbcc58a30fc78f8224939d31d3152bdafc01fd1/matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df", size = 9094637, upload-time = "2024-12-13T05:55:33.701Z" }, + { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311, upload-time = "2024-12-13T05:55:36.737Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3a/2757d3f7d388b14dd48f5a83bea65b6d69f000e86b8f28f74d86e0d375bd/matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb", size = 7919989, upload-time = "2024-12-13T05:55:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/24/28/f5077c79a4f521589a37fe1062d6a6ea3534e068213f7357e7cfffc2e17a/matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a", size = 7809417, upload-time = "2024-12-13T05:55:42.412Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/c523fd2963156692916a8eb7d4069084cf729359f7955cf09075deddfeaf/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c", size = 8226258, upload-time = "2024-12-13T05:55:47.259Z" }, + { url = "https://files.pythonhosted.org/packages/f6/88/499bf4b8fa9349b6f5c0cf4cead0ebe5da9d67769129f1b5651e5ac51fbc/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764", size = 8335849, upload-time = "2024-12-13T05:55:49.763Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9f/20a4156b9726188646a030774ee337d5ff695a965be45ce4dbcb9312c170/matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041", size = 9102152, upload-time = "2024-12-13T05:55:51.997Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987, upload-time = "2024-12-13T05:55:55.941Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/501b465c9fef28f158e414ea3a417913dc2ac748564c7ed41535f23445b4/matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c", size = 7885919, upload-time = "2024-12-13T05:55:59.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/36/236fbd868b6c91309a5206bd90c3f881f4f44b2d997cd1d6239ef652f878/matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7", size = 7771486, upload-time = "2024-12-13T05:56:04.264Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/105caf2d54d5ed11d9f4335398f5103001a03515f2126c936a752ccf1461/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e", size = 8201838, upload-time = "2024-12-13T05:56:06.792Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a7/bb01188fb4013d34d274caf44a2f8091255b0497438e8b6c0a7c1710c692/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c", size = 8314492, upload-time = "2024-12-13T05:56:09.964Z" }, + { url = "https://files.pythonhosted.org/packages/33/19/02e1a37f7141fc605b193e927d0a9cdf9dc124a20b9e68793f4ffea19695/matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb", size = 9092500, upload-time = "2024-12-13T05:56:13.55Z" }, + { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962, upload-time = "2024-12-13T05:56:16.358Z" }, + { url = "https://files.pythonhosted.org/packages/0c/22/2ef6a364cd3f565442b0b055e0599744f1e4314ec7326cdaaa48a4d864d7/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c", size = 7877995, upload-time = "2024-12-13T05:56:18.805Z" }, + { url = "https://files.pythonhosted.org/packages/87/b8/2737456e566e9f4d94ae76b8aa0d953d9acb847714f9a7ad80184474f5be/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca", size = 7769300, upload-time = "2024-12-13T05:56:21.315Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1f/e709c6ec7b5321e6568769baa288c7178e60a93a9da9e682b39450da0e29/matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db", size = 8313423, upload-time = "2024-12-13T05:56:26.719Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624, upload-time = "2024-12-13T05:56:29.359Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.10'" }, + { name = "fonttools", version = "4.61.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "kiwisolver", version = "1.4.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "12.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyparsing", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "maturin" +version = "1.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/44/c593afce7d418ae6016b955c978055232359ad28c707a9ac6643fc60512d/maturin-1.10.2.tar.gz", hash = "sha256:259292563da89850bf8f7d37aa4ddba22905214c1e180b1c8f55505dfd8c0e81", size = 217835, upload-time = "2025-11-19T11:53:17.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/74/7f7e93019bb71aa072a7cdf951cbe4c9a8d5870dd86c66ec67002153487f/maturin-1.10.2-py3-none-linux_armv6l.whl", hash = "sha256:11c73815f21a755d2129c410e6cb19dbfacbc0155bfc46c706b69930c2eb794b", size = 8763201, upload-time = "2025-11-19T11:52:42.98Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/1d1b64dbb6518ee633bfde8787e251ae59428818fea7a6bdacb8008a09bd/maturin-1.10.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7fbd997c5347649ee7987bd05a92bd5b8b07efa4ac3f8bcbf6196e07eb573d89", size = 17072583, upload-time = "2025-11-19T11:52:45.636Z" }, + { url = "https://files.pythonhosted.org/packages/7c/45/2418f0d6e1cbdf890205d1dc73ebea6778bb9ce80f92e866576c701ded72/maturin-1.10.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3ce9b2ad4fb9c341f450a6d32dc3edb409a2d582a81bc46ba55f6e3b6196b22", size = 8827021, upload-time = "2025-11-19T11:52:48.143Z" }, + { url = "https://files.pythonhosted.org/packages/7f/83/14c96ddc93b38745d8c3b85126f7d78a94f809a49dc9644bb22b0dc7b78c/maturin-1.10.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:f0d1b7b5f73c8d30a7e71cd2a2189a7f0126a3a3cd8b3d6843e7e1d4db50f759", size = 8751780, upload-time = "2025-11-19T11:52:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/46/8d/753148c0d0472acd31a297f6d11c3263cd2668d38278ed29d523625f7290/maturin-1.10.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:efcd496a3202ffe0d0489df1f83d08b91399782fb2dd545d5a1e7bf6fd81af39", size = 9241884, upload-time = "2025-11-19T11:52:53.946Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f9/f5ca9fe8cad70cac6f3b6008598cc708f8a74dd619baced99784a6253f23/maturin-1.10.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a41ec70d99e27c05377be90f8e3c3def2a7bae4d0d9d5ea874aaf2d1da625d5c", size = 8671736, upload-time = "2025-11-19T11:52:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/0a/76/f59cbcfcabef0259c3971f8b5754c85276a272028d8363386b03ec4e9947/maturin-1.10.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:07a82864352feeaf2167247c8206937ef6c6ae9533025d416b7004ade0ea601d", size = 8633475, upload-time = "2025-11-19T11:53:00.389Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/96cd959ad1dda6c12301860a74afece200a3209d84b393beedd5d7d915c0/maturin-1.10.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:04df81ee295dcda37828bd025a4ac688ea856e3946e4cb300a8f44a448de0069", size = 11177118, upload-time = "2025-11-19T11:53:03.014Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b6/144f180f36314be183f5237011528f0e39fe5fd2e74e65c3b44a5795971e/maturin-1.10.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96e1d391e4c1fa87edf2a37e4d53d5f2e5f39dd880b9d8306ac9f8eb212d23f8", size = 9320218, upload-time = "2025-11-19T11:53:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2d/2c483c1b3118e2e10fd8219d5291843f5f7c12284113251bf506144a3ac1/maturin-1.10.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a217aa7c42aa332fb8e8377eb07314e1f02cf0fe036f614aca4575121952addd", size = 8985266, upload-time = "2025-11-19T11:53:07.618Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/1d0222521e112cd058b56e8d96c72cf9615f799e3b557adb4b16004f42aa/maturin-1.10.2-py3-none-win32.whl", hash = "sha256:da031771d9fb6ddb1d373638ec2556feee29e4507365cd5749a2d354bcadd818", size = 7667897, upload-time = "2025-11-19T11:53:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ec/c6c973b1def0d04533620b439d5d7aebb257657ba66710885394514c8045/maturin-1.10.2-py3-none-win_amd64.whl", hash = "sha256:da777766fd584440dc9fecd30059a94f85e4983f58b09e438ae38ee4b494024c", size = 8908416, upload-time = "2025-11-19T11:53:12.862Z" }, + { url = "https://files.pythonhosted.org/packages/1b/01/7da60c9f7d5dc92dfa5e8888239fd0fb2613ee19e44e6db5c2ed5595fab3/maturin-1.10.2-py3-none-win_arm64.whl", hash = "sha256:a4c29a770ea2c76082e0afc6d4efd8ee94405588bfae00d10828f72e206c739b", size = 7506680, upload-time = "2025-11-19T11:53:15.403Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mmh3" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/af/f28c2c2f51f31abb4725f9a64bc7863d5f491f6539bd26aee2a1d21a649e/mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8", size = 33582, upload-time = "2025-07-29T07:43:48.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/2b/870f0ff5ecf312c58500f45950751f214b7068665e66e9bfd8bc2595587c/mmh3-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81c504ad11c588c8629536b032940f2a359dda3b6cbfd4ad8f74cb24dcd1b0bc", size = 56119, upload-time = "2025-07-29T07:41:39.117Z" }, + { url = "https://files.pythonhosted.org/packages/3b/88/eb9a55b3f3cf43a74d6bfa8db0e2e209f966007777a1dc897c52c008314c/mmh3-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b898cecff57442724a0f52bf42c2de42de63083a91008fb452887e372f9c328", size = 40634, upload-time = "2025-07-29T07:41:40.626Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4c/8e4b3878bf8435c697d7ce99940a3784eb864521768069feaccaff884a17/mmh3-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be1374df449465c9f2500e62eee73a39db62152a8bdfbe12ec5b5c1cd451344d", size = 40080, upload-time = "2025-07-29T07:41:41.791Z" }, + { url = "https://files.pythonhosted.org/packages/45/ac/0a254402c8c5ca424a0a9ebfe870f5665922f932830f0a11a517b6390a09/mmh3-5.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0d753ad566c721faa33db7e2e0eddd74b224cdd3eaf8481d76c926603c7a00e", size = 95321, upload-time = "2025-07-29T07:41:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/39/8e/29306d5eca6dfda4b899d22c95b5420db4e0ffb7e0b6389b17379654ece5/mmh3-5.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dfbead5575f6470c17e955b94f92d62a03dfc3d07f2e6f817d9b93dc211a1515", size = 101220, upload-time = "2025-07-29T07:41:43.572Z" }, + { url = "https://files.pythonhosted.org/packages/49/f7/0dd1368e531e52a17b5b8dd2f379cce813bff2d0978a7748a506f1231152/mmh3-5.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7434a27754049144539d2099a6d2da5d88b8bdeedf935180bf42ad59b3607aa3", size = 103991, upload-time = "2025-07-29T07:41:44.914Z" }, + { url = "https://files.pythonhosted.org/packages/35/06/abc7122c40f4abbfcef01d2dac6ec0b77ede9757e5be8b8a40a6265b1274/mmh3-5.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cadc16e8ea64b5d9a47363013e2bea469e121e6e7cb416a7593aeb24f2ad122e", size = 110894, upload-time = "2025-07-29T07:41:45.849Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2f/837885759afa4baccb8e40456e1cf76a4f3eac835b878c727ae1286c5f82/mmh3-5.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d765058da196f68dc721116cab335e696e87e76720e6ef8ee5a24801af65e63d", size = 118327, upload-time = "2025-07-29T07:41:47.224Z" }, + { url = "https://files.pythonhosted.org/packages/40/cc/5683ba20a21bcfb3f1605b1c474f46d30354f728a7412201f59f453d405a/mmh3-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b0c53fe0994beade1ad7c0f13bd6fec980a0664bfbe5a6a7d64500b9ab76772", size = 101701, upload-time = "2025-07-29T07:41:48.259Z" }, + { url = "https://files.pythonhosted.org/packages/0e/24/99ab3fb940150aec8a26dbdfc39b200b5592f6aeb293ec268df93e054c30/mmh3-5.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:49037d417419863b222ae47ee562b2de9c3416add0a45c8d7f4e864be8dc4f89", size = 96712, upload-time = "2025-07-29T07:41:49.467Z" }, + { url = "https://files.pythonhosted.org/packages/61/04/d7c4cb18f1f001ede2e8aed0f9dbbfad03d161c9eea4fffb03f14f4523e5/mmh3-5.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6ecb4e750d712abde046858ee6992b65c93f1f71b397fce7975c3860c07365d2", size = 110302, upload-time = "2025-07-29T07:41:50.387Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bf/4dac37580cfda74425a4547500c36fa13ef581c8a756727c37af45e11e9a/mmh3-5.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:382a6bb3f8c6532ea084e7acc5be6ae0c6effa529240836d59352398f002e3fc", size = 111929, upload-time = "2025-07-29T07:41:51.348Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b1/49f0a582c7a942fb71ddd1ec52b7d21d2544b37d2b2d994551346a15b4f6/mmh3-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7733ec52296fc1ba22e9b90a245c821adbb943e98c91d8a330a2254612726106", size = 100111, upload-time = "2025-07-29T07:41:53.139Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/ccec09f438caeb2506f4c63bb3b99aa08a9e09880f8fc047295154756210/mmh3-5.2.0-cp310-cp310-win32.whl", hash = "sha256:127c95336f2a98c51e7682341ab7cb0be3adb9df0819ab8505a726ed1801876d", size = 40783, upload-time = "2025-07-29T07:41:54.463Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f4/8d39a32c8203c1cdae88fdb04d1ea4aa178c20f159df97f4c5a2eaec702c/mmh3-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:419005f84ba1cab47a77465a2a843562dadadd6671b8758bf179d82a15ca63eb", size = 41549, upload-time = "2025-07-29T07:41:55.295Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/30efb1cd945e193f62574144dd92a0c9ee6463435e4e8ffce9b9e9f032f0/mmh3-5.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:d22c9dcafed659fadc605538946c041722b6d1104fe619dbf5cc73b3c8a0ded8", size = 39335, upload-time = "2025-07-29T07:41:56.194Z" }, + { url = "https://files.pythonhosted.org/packages/f7/87/399567b3796e134352e11a8b973cd470c06b2ecfad5468fe580833be442b/mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1", size = 56107, upload-time = "2025-07-29T07:41:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/c3/09/830af30adf8678955b247d97d3d9543dd2fd95684f3cd41c0cd9d291da9f/mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051", size = 40635, upload-time = "2025-07-29T07:41:57.903Z" }, + { url = "https://files.pythonhosted.org/packages/07/14/eaba79eef55b40d653321765ac5e8f6c9ac38780b8a7c2a2f8df8ee0fb72/mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10", size = 40078, upload-time = "2025-07-29T07:41:58.772Z" }, + { url = "https://files.pythonhosted.org/packages/bb/26/83a0f852e763f81b2265d446b13ed6d49ee49e1fc0c47b9655977e6f3d81/mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c", size = 97262, upload-time = "2025-07-29T07:41:59.678Z" }, + { url = "https://files.pythonhosted.org/packages/00/7d/b7133b10d12239aeaebf6878d7eaf0bf7d3738c44b4aba3c564588f6d802/mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762", size = 103118, upload-time = "2025-07-29T07:42:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3e/62f0b5dce2e22fd5b7d092aba285abd7959ea2b17148641e029f2eab1ffa/mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4", size = 106072, upload-time = "2025-07-29T07:42:02.601Z" }, + { url = "https://files.pythonhosted.org/packages/66/84/ea88bb816edfe65052c757a1c3408d65c4201ddbd769d4a287b0f1a628b2/mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363", size = 112925, upload-time = "2025-07-29T07:42:03.632Z" }, + { url = "https://files.pythonhosted.org/packages/2e/13/c9b1c022807db575fe4db806f442d5b5784547e2e82cff36133e58ea31c7/mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8", size = 120583, upload-time = "2025-07-29T07:42:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/0e2dfe1a38f6a78788b7eb2b23432cee24623aeabbc907fed07fc17d6935/mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed", size = 99127, upload-time = "2025-07-29T07:42:05.929Z" }, + { url = "https://files.pythonhosted.org/packages/77/27/aefb7d663b67e6a0c4d61a513c83e39ba2237e8e4557fa7122a742a23de5/mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646", size = 98544, upload-time = "2025-07-29T07:42:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/ab/97/a21cc9b1a7c6e92205a1b5fa030cdf62277d177570c06a239eca7bd6dd32/mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b", size = 106262, upload-time = "2025-07-29T07:42:07.804Z" }, + { url = "https://files.pythonhosted.org/packages/43/18/db19ae82ea63c8922a880e1498a75342311f8aa0c581c4dd07711473b5f7/mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779", size = 109824, upload-time = "2025-07-29T07:42:08.735Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f5/41dcf0d1969125fc6f61d8618b107c79130b5af50b18a4651210ea52ab40/mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2", size = 97255, upload-time = "2025-07-29T07:42:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/cce9eaa0efac1f0e735bb178ef9d1d2887b4927fe0ec16609d5acd492dda/mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28", size = 40779, upload-time = "2025-07-29T07:42:10.546Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e9/3fa0290122e6d5a7041b50ae500b8a9f4932478a51e48f209a3879fe0b9b/mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee", size = 41549, upload-time = "2025-07-29T07:42:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/3a/54/c277475b4102588e6f06b2e9095ee758dfe31a149312cdbf62d39a9f5c30/mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9", size = 39336, upload-time = "2025-07-29T07:42:12.209Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6a/d5aa7edb5c08e0bd24286c7d08341a0446f9a2fbbb97d96a8a6dd81935ee/mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be", size = 56141, upload-time = "2025-07-29T07:42:13.456Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/131d0fae6447bc4a7299ebdb1a6fb9d08c9f8dcf97d75ea93e8152ddf7ab/mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd", size = 40681, upload-time = "2025-07-29T07:42:14.306Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6f/9221445a6bcc962b7f5ff3ba18ad55bba624bacdc7aa3fc0a518db7da8ec/mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96", size = 40062, upload-time = "2025-07-29T07:42:15.08Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d4/6bb2d0fef81401e0bb4c297d1eb568b767de4ce6fc00890bc14d7b51ecc4/mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094", size = 97333, upload-time = "2025-07-29T07:42:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/44/e0/ccf0daff8134efbb4fbc10a945ab53302e358c4b016ada9bf97a6bdd50c1/mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037", size = 103310, upload-time = "2025-07-29T07:42:17.796Z" }, + { url = "https://files.pythonhosted.org/packages/02/63/1965cb08a46533faca0e420e06aff8bbaf9690a6f0ac6ae6e5b2e4544687/mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773", size = 106178, upload-time = "2025-07-29T07:42:19.281Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/c883ad8e2c234013f27f92061200afc11554ea55edd1bcf5e1accd803a85/mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5", size = 113035, upload-time = "2025-07-29T07:42:20.356Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/1ccade8b1fa625d634a18bab7bf08a87457e09d5ec8cf83ca07cbea9d400/mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50", size = 120784, upload-time = "2025-07-29T07:42:21.377Z" }, + { url = "https://files.pythonhosted.org/packages/77/1c/919d9171fcbdcdab242e06394464ccf546f7d0f3b31e0d1e3a630398782e/mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765", size = 99137, upload-time = "2025-07-29T07:42:22.344Z" }, + { url = "https://files.pythonhosted.org/packages/66/8a/1eebef5bd6633d36281d9fc83cf2e9ba1ba0e1a77dff92aacab83001cee4/mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43", size = 98664, upload-time = "2025-07-29T07:42:23.269Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/a5d981563e2ee682b21fb65e29cc0f517a6734a02b581359edd67f9d0360/mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4", size = 106459, upload-time = "2025-07-29T07:42:24.238Z" }, + { url = "https://files.pythonhosted.org/packages/24/31/342494cd6ab792d81e083680875a2c50fa0c5df475ebf0b67784f13e4647/mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3", size = 110038, upload-time = "2025-07-29T07:42:25.629Z" }, + { url = "https://files.pythonhosted.org/packages/28/44/efda282170a46bb4f19c3e2b90536513b1d821c414c28469a227ca5a1789/mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c", size = 97545, upload-time = "2025-07-29T07:42:27.04Z" }, + { url = "https://files.pythonhosted.org/packages/68/8f/534ae319c6e05d714f437e7206f78c17e66daca88164dff70286b0e8ea0c/mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49", size = 40805, upload-time = "2025-07-29T07:42:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/f6abdcfefcedab3c964868048cfe472764ed358c2bf6819a70dd4ed4ed3a/mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3", size = 41597, upload-time = "2025-07-29T07:42:28.894Z" }, + { url = "https://files.pythonhosted.org/packages/15/fd/f7420e8cbce45c259c770cac5718badf907b302d3a99ec587ba5ce030237/mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0", size = 39350, upload-time = "2025-07-29T07:42:29.794Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/27f6ab93995ef6ad9f940e96593c5dd24744d61a7389532b0fec03745607/mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065", size = 40874, upload-time = "2025-07-29T07:42:30.662Z" }, + { url = "https://files.pythonhosted.org/packages/11/9c/03d13bcb6a03438bc8cac3d2e50f80908d159b31a4367c2e1a7a077ded32/mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de", size = 42012, upload-time = "2025-07-29T07:42:31.539Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/0865d9765408a7d504f1789944e678f74e0888b96a766d578cb80b040999/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044", size = 39197, upload-time = "2025-07-29T07:42:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/76c3207bd186f98b908b6706c2317abb73756d23a4e68ea2bc94825b9015/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73", size = 39840, upload-time = "2025-07-29T07:42:33.227Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0d/574b6cce5555c9f2b31ea189ad44986755eb14e8862db28c8b834b8b64dc/mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504", size = 40644, upload-time = "2025-07-29T07:42:34.099Z" }, + { url = "https://files.pythonhosted.org/packages/52/82/3731f8640b79c46707f53ed72034a58baad400be908c87b0088f1f89f986/mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b", size = 56153, upload-time = "2025-07-29T07:42:35.031Z" }, + { url = "https://files.pythonhosted.org/packages/4f/34/e02dca1d4727fd9fdeaff9e2ad6983e1552804ce1d92cc796e5b052159bb/mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05", size = 40684, upload-time = "2025-07-29T07:42:35.914Z" }, + { url = "https://files.pythonhosted.org/packages/8f/36/3dee40767356e104967e6ed6d102ba47b0b1ce2a89432239b95a94de1b89/mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814", size = 40057, upload-time = "2025-07-29T07:42:36.755Z" }, + { url = "https://files.pythonhosted.org/packages/31/58/228c402fccf76eb39a0a01b8fc470fecf21965584e66453b477050ee0e99/mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093", size = 97344, upload-time = "2025-07-29T07:42:37.675Z" }, + { url = "https://files.pythonhosted.org/packages/34/82/fc5ce89006389a6426ef28e326fc065b0fbaaed230373b62d14c889f47ea/mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54", size = 103325, upload-time = "2025-07-29T07:42:38.591Z" }, + { url = "https://files.pythonhosted.org/packages/09/8c/261e85777c6aee1ebd53f2f17e210e7481d5b0846cd0b4a5c45f1e3761b8/mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a", size = 106240, upload-time = "2025-07-29T07:42:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/70/73/2f76b3ad8a3d431824e9934403df36c0ddacc7831acf82114bce3c4309c8/mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908", size = 113060, upload-time = "2025-07-29T07:42:40.585Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/7ea61a34e90e50a79a9d87aa1c0b8139a7eaf4125782b34b7d7383472633/mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5", size = 120781, upload-time = "2025-07-29T07:42:41.618Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5b/ae1a717db98c7894a37aeedbd94b3f99e6472a836488f36b6849d003485b/mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a", size = 99174, upload-time = "2025-07-29T07:42:42.587Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/000cce1d799fceebb6d4487ae29175dd8e81b48e314cba7b4da90bcf55d7/mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266", size = 98734, upload-time = "2025-07-29T07:42:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/79/19/0dc364391a792b72fbb22becfdeacc5add85cc043cd16986e82152141883/mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5", size = 106493, upload-time = "2025-07-29T07:42:45.07Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b1/bc8c28e4d6e807bbb051fefe78e1156d7f104b89948742ad310612ce240d/mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9", size = 110089, upload-time = "2025-07-29T07:42:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a2/d20f3f5c95e9c511806686c70d0a15479cc3941c5f322061697af1c1ff70/mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290", size = 97571, upload-time = "2025-07-29T07:42:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/7b/23/665296fce4f33488deec39a750ffd245cfc07aafb0e3ef37835f91775d14/mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051", size = 40806, upload-time = "2025-07-29T07:42:48.166Z" }, + { url = "https://files.pythonhosted.org/packages/59/b0/92e7103f3b20646e255b699e2d0327ce53a3f250e44367a99dc8be0b7c7a/mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081", size = 41600, upload-time = "2025-07-29T07:42:49.371Z" }, + { url = "https://files.pythonhosted.org/packages/99/22/0b2bd679a84574647de538c5b07ccaa435dbccc37815067fe15b90fe8dad/mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b", size = 39349, upload-time = "2025-07-29T07:42:50.268Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/a20db059a8a47048aaf550da14a145b56e9c7386fb8280d3ce2962dcebf7/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078", size = 39209, upload-time = "2025-07-29T07:42:51.559Z" }, + { url = "https://files.pythonhosted.org/packages/98/dd/e5094799d55c7482d814b979a0fd608027d0af1b274bfb4c3ea3e950bfd5/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501", size = 39843, upload-time = "2025-07-29T07:42:52.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/7844d7f832c85400e7cc89a1348e4e1fdd38c5a38415bb5726bbb8fcdb6c/mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b", size = 40648, upload-time = "2025-07-29T07:42:53.392Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bf/71f791f48a21ff3190ba5225807cbe4f7223360e96862c376e6e3fb7efa7/mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770", size = 56164, upload-time = "2025-07-29T07:42:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/70/1f/f87e3d34d83032b4f3f0f528c6d95a98290fcacf019da61343a49dccfd51/mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110", size = 40692, upload-time = "2025-07-29T07:42:55.234Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e2/db849eaed07117086f3452feca8c839d30d38b830ac59fe1ce65af8be5ad/mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647", size = 40068, upload-time = "2025-07-29T07:42:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/df/6b/209af927207af77425b044e32f77f49105a0b05d82ff88af6971d8da4e19/mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63", size = 97367, upload-time = "2025-07-29T07:42:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e0/78adf4104c425606a9ce33fb351f790c76a6c2314969c4a517d1ffc92196/mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12", size = 103306, upload-time = "2025-07-29T07:42:58.522Z" }, + { url = "https://files.pythonhosted.org/packages/a3/79/c2b89f91b962658b890104745b1b6c9ce38d50a889f000b469b91eeb1b9e/mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22", size = 106312, upload-time = "2025-07-29T07:42:59.552Z" }, + { url = "https://files.pythonhosted.org/packages/4b/14/659d4095528b1a209be90934778c5ffe312177d51e365ddcbca2cac2ec7c/mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5", size = 113135, upload-time = "2025-07-29T07:43:00.745Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6f/cd7734a779389a8a467b5c89a48ff476d6f2576e78216a37551a97e9e42a/mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07", size = 120775, upload-time = "2025-07-29T07:43:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ca/8256e3b96944408940de3f9291d7e38a283b5761fe9614d4808fcf27bd62/mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935", size = 99178, upload-time = "2025-07-29T07:43:03.182Z" }, + { url = "https://files.pythonhosted.org/packages/8a/32/39e2b3cf06b6e2eb042c984dab8680841ac2a0d3ca6e0bea30db1f27b565/mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7", size = 98738, upload-time = "2025-07-29T07:43:04.207Z" }, + { url = "https://files.pythonhosted.org/packages/61/d3/7bbc8e0e8cf65ebbe1b893ffa0467b7ecd1bd07c3bbf6c9db4308ada22ec/mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5", size = 106510, upload-time = "2025-07-29T07:43:05.656Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/b97e53724b52374e2f3859046f0eb2425192da356cb19784d64bc17bb1cf/mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384", size = 110053, upload-time = "2025-07-29T07:43:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/ac/62/3688c7d975ed195155671df68788c83fed6f7909b6ec4951724c6860cb97/mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e", size = 97546, upload-time = "2025-07-29T07:43:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3b/c6153250f03f71a8b7634cded82939546cdfba02e32f124ff51d52c6f991/mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0", size = 41422, upload-time = "2025-07-29T07:43:09.216Z" }, + { url = "https://files.pythonhosted.org/packages/74/01/a27d98bab083a435c4c07e9d1d720d4c8a578bf4c270bae373760b1022be/mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b", size = 42135, upload-time = "2025-07-29T07:43:10.183Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c9/dbba5507e95429b8b380e2ba091eff5c20a70a59560934dff0ad8392b8c8/mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115", size = 39879, upload-time = "2025-07-29T07:43:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d1/c8c0ef839c17258b9de41b84f663574fabcf8ac2007b7416575e0f65ff6e/mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932", size = 57696, upload-time = "2025-07-29T07:43:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/95e2b9ff201e89f9fe37036037ab61a6c941942b25cdb7b6a9df9b931993/mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c", size = 41421, upload-time = "2025-07-29T07:43:13.269Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/9be23ad0b7001a4b22752e7693be232428ecc0a35068a4ff5c2f14ef8b20/mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be", size = 40853, upload-time = "2025-07-29T07:43:14.888Z" }, + { url = "https://files.pythonhosted.org/packages/ac/1b/96b32058eda1c1dee8264900c37c359a7325c1f11f5ff14fd2be8e24eff9/mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb", size = 109694, upload-time = "2025-07-29T07:43:15.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6f/a2ae44cd7dad697b6dea48390cbc977b1e5ca58fda09628cbcb2275af064/mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65", size = 117438, upload-time = "2025-07-29T07:43:16.865Z" }, + { url = "https://files.pythonhosted.org/packages/a0/08/bfb75451c83f05224a28afeaf3950c7b793c0b71440d571f8e819cfb149a/mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991", size = 120409, upload-time = "2025-07-29T07:43:18.207Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/8b118b69b2ff8df568f742387d1a159bc654a0f78741b31437dd047ea28e/mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645", size = 125909, upload-time = "2025-07-29T07:43:19.39Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/168cc0b6a30650032e351a3b89b8a47382da541993a03af91e1ba2501234/mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3", size = 135331, upload-time = "2025-07-29T07:43:20.435Z" }, + { url = "https://files.pythonhosted.org/packages/31/05/e3a9849b1c18a7934c64e831492c99e67daebe84a8c2f2c39a7096a830e3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279", size = 110085, upload-time = "2025-07-29T07:43:21.92Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/a96bcc306e3404601418b2a9a370baec92af84204528ba659fdfe34c242f/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513", size = 111195, upload-time = "2025-07-29T07:43:23.066Z" }, + { url = "https://files.pythonhosted.org/packages/af/29/0fd49801fec5bff37198684e0849b58e0dab3a2a68382a357cfffb0fafc3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db", size = 116919, upload-time = "2025-07-29T07:43:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/2d/04/4f3c32b0a2ed762edca45d8b46568fc3668e34f00fb1e0a3b5451ec1281c/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667", size = 123160, upload-time = "2025-07-29T07:43:25.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/76/3d29eaa38821730633d6a240d36fa8ad2807e9dfd432c12e1a472ed211eb/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5", size = 110206, upload-time = "2025-07-29T07:43:26.699Z" }, + { url = "https://files.pythonhosted.org/packages/44/1c/ccf35892684d3a408202e296e56843743e0b4fb1629e59432ea88cdb3909/mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7", size = 41970, upload-time = "2025-07-29T07:43:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/b9e4f1e5adb5e21eb104588fcee2cd1eaa8308255173481427d5ecc4284e/mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d", size = 43063, upload-time = "2025-07-29T07:43:28.582Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0e61d9a4e29c8679356795a40e48f647b4aad58d71bfc969f0f8f56fb912/mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9", size = 40455, upload-time = "2025-07-29T07:43:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/4bad09e880b648eeb55393a644c08efbd7da302fc405c8d2f6555521bb98/mmh3-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3c6041fd9d5fb5fcac57d5c80f521a36b74aea06b8566431c63e4ffc49aced51", size = 56117, upload-time = "2025-07-29T07:43:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/b2/43/97cacd1fa2994b4ec110334388e126fe000ddf041829721e2e59e46b0a7c/mmh3-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58477cf9ef16664d1ce2b038f87d2dc96d70fe50733a34a7f07da6c9a5e3538c", size = 40634, upload-time = "2025-07-29T07:43:31.917Z" }, + { url = "https://files.pythonhosted.org/packages/e9/03/2a52e464b0e23f9838267adf75f942c5addc2c1f009a48d1ef5c331084fb/mmh3-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be7d3dca9358e01dab1bad881fb2b4e8730cec58d36dd44482bc068bfcd3bc65", size = 40075, upload-time = "2025-07-29T07:43:32.9Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/c0c00f7eb436a0adf64d8a877673ac76096bf86aca57b6a2c80786d69242/mmh3-5.2.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:931d47e08c9c8a67bf75d82f0ada8399eac18b03388818b62bfa42882d571d72", size = 95112, upload-time = "2025-07-29T07:43:33.815Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f3/116cc1171bcb41a9cec10c46ee1d8bb5185d70c15848ff66d15ab7afb6fd/mmh3-5.2.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd966df3489ec13848d6c6303429bbace94a153f43d1ae2a55115fd36fd5ca5d", size = 101006, upload-time = "2025-07-29T07:43:34.876Z" }, + { url = "https://files.pythonhosted.org/packages/41/34/b38a0c5c323666e632cc07d4fd337c4af0b300619c7b8b7a1d9a2db1ac1a/mmh3-5.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c677d78887244bf3095020b73c42b505b700f801c690f8eaa90ad12d3179612f", size = 103782, upload-time = "2025-07-29T07:43:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/25/d6/42b5ae7219ec87f756ffafcf7471b7fd3386e352653522d155f4897e06d0/mmh3-5.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63830f846797187c5d3e2dae50f0848fdc86032f5bfdc58ae352f02f857e9025", size = 110660, upload-time = "2025-07-29T07:43:37.103Z" }, + { url = "https://files.pythonhosted.org/packages/8f/55/daea1ee478328f7ed3b5422f080a3f892e02bc1542f0bc5a1be083a05758/mmh3-5.2.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c3f563e8901960e2eaa64c8e8821895818acabeb41c96f2efbb936f65dbe486c", size = 118107, upload-time = "2025-07-29T07:43:38.173Z" }, + { url = "https://files.pythonhosted.org/packages/46/f1/930d3395a0aaef49db41019e94a7b46ac35b9a64c213a620eacac34078c0/mmh3-5.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96f1e1ac44cbb42bcc406e509f70c9af42c594e72ccc7b1257f97554204445f0", size = 101448, upload-time = "2025-07-29T07:43:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e4/543bf2622a1645fa560c26fe5dc2919c8c9eb2f9ac129778ce6acc9848fc/mmh3-5.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7bbb0df897944b5ec830f3ad883e32c5a7375370a521565f5fe24443bfb2c4f7", size = 96474, upload-time = "2025-07-29T07:43:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/16/d8/9c552bd64c86bb03fba08d4b702efd65b09ed54c6969df0d1ec7fa8c0ae4/mmh3-5.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1fae471339ae1b9c641f19cf46dfe6ffd7f64b1fba7c4333b99fa3dd7f21ae0a", size = 110049, upload-time = "2025-07-29T07:43:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/6b/47/8a012b9c4d9c9b704ffcd71cad861ef120b2bd417d081bdb3aaa9e396fe6/mmh3-5.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:aa6e5d31fdc5ed9e3e95f9873508615a778fe9b523d52c17fc770a3eb39ab6e4", size = 111683, upload-time = "2025-07-29T07:43:43.228Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fc/4ad1bd01976484d0568a7d18d5a8597da1e65e76ac763114573dcd09d225/mmh3-5.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:746a5ee71c6d1103d9b560fa147881b5e68fd35da56e54e03d5acefad0e7c055", size = 99883, upload-time = "2025-07-29T07:43:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/4fbd0f74c7e9c35f5f70eb77509b7a706ef76ee86957a79e228f47cf037f/mmh3-5.2.0-cp39-cp39-win32.whl", hash = "sha256:10983c10f5c77683bd845751905ba535ec47409874acc759d5ce3ff7ef34398a", size = 40790, upload-time = "2025-07-29T07:43:45.296Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/0f593606dbd3a4259301ffb61678433656dc4a2c6da022fa7a122de7ffb4/mmh3-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fdfd3fb739f4e22746e13ad7ba0c6eedf5f454b18d11249724a388868e308ee4", size = 41563, upload-time = "2025-07-29T07:43:46.599Z" }, + { url = "https://files.pythonhosted.org/packages/07/e6/ff066b72d86f0a19d3e4b6f3af073a9a328cb3cb4b068e25972866fcd517/mmh3-5.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:33576136c06b46a7046b6d83a3d75fbca7d25f84cec743f1ae156362608dc6d2", size = 39340, upload-time = "2025-07-29T07:43:47.512Z" }, +] + +[[package]] +name = "mmh3" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/bb/88ee54afa5644b0f35ab5b435f208394feb963e5bb47c4e404deb625ffa4/mmh3-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5d87a3584093e1a89987e3d36d82c98d9621b2cb944e22a420aa1401e096758f", size = 56080, upload-time = "2026-03-05T15:53:40.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bf/5404c2fd6ac84819e8ff1b7e34437b37cf55a2b11318894909e7bb88de3f/mmh3-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30e4d2084df019880d55f6f7bea35328d9b464ebee090baa372c096dc77556fb", size = 40462, upload-time = "2026-03-05T15:53:41.751Z" }, + { url = "https://files.pythonhosted.org/packages/de/0b/52bffad0b52ae4ea53e222b594bd38c08ecac1fc410323220a7202e43da5/mmh3-5.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bbc17250b10d3466875a40a52520a6bac3c02334ca709207648abd3c223ed5c", size = 40077, upload-time = "2026-03-05T15:53:42.753Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9e/326c93d425b9fa4cbcdc71bc32aaba520db37577d632a24d25d927594eca/mmh3-5.2.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76219cd1eefb9bf4af7856e3ae563d15158efa145c0aab01e9933051a1954045", size = 95302, upload-time = "2026-03-05T15:53:43.867Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b1/e20d5f0d19c4c0f3df213fa7dcfa0942c4fb127d38e11f398ae8ddf6cccc/mmh3-5.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb9d44c25244e11c8be3f12c938ca8ba8404620ef8092245d2093c6ab3df260f", size = 101174, upload-time = "2026-03-05T15:53:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4a/1a9bb3e33c18b1e1cee2c249a3053c4d4d9c93ecb30738f39a62249a7e86/mmh3-5.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5d542bf2abd0fd0361e8017d03f7cb5786214ceb4a40eef1539d6585d93386", size = 103979, upload-time = "2026-03-05T15:53:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/dab9ee7545429e7acdd38d23d0104471d31de09a0c695f1b751e0ff34532/mmh3-5.2.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:08043f7cb1fb9467c3fbbbaea7896986e7fbc81f4d3fd9289a73d9110ab6207a", size = 110898, upload-time = "2026-03-05T15:53:47.443Z" }, + { url = "https://files.pythonhosted.org/packages/72/08/408f11af7fe9e76b883142bb06536007cc7f237be2a5e9ad4e837716e627/mmh3-5.2.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:add7ac388d1e0bf57259afbcf9ed05621a3bf11ce5ee337e7536f1e1aaf056b0", size = 118308, upload-time = "2026-03-05T15:53:49.1Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/0551be7fe0000736d9ad12ffa1f130d7a0c17b49193d6dc41c82bd9404c6/mmh3-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41105377f6282e8297f182e393a79cfffd521dde37ace52b106373bdcd9ca5cb", size = 101671, upload-time = "2026-03-05T15:53:50.317Z" }, + { url = "https://files.pythonhosted.org/packages/44/17/6e4f80c4e6ad590139fa2017c3aeca54e7cc9ef68e08aa142a0c90f40a97/mmh3-5.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3cb61db880ec11e984348227b333259994c2c85caa775eb7875decb3768db890", size = 96682, upload-time = "2026-03-05T15:53:51.48Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a7/b82fccd38c1fa815de72e94ebe9874562964a10e21e6c1bc3b01d3f15a0e/mmh3-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b5378de2b139c3a830f0209c1e91f7705919a4b3e563a10955104f5097a70a", size = 110287, upload-time = "2026-03-05T15:53:52.68Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a1/2644069031c8cec0be46f0346f568a53f42fddd843f03cc890306699c1e2/mmh3-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e904f2417f0d6f6d514f3f8b836416c360f306ddaee1f84de8eef1e722d212e5", size = 111899, upload-time = "2026-03-05T15:53:53.791Z" }, + { url = "https://files.pythonhosted.org/packages/51/7b/6614f3eb8fb33f931fa7616c6d477247e48ec6c5082b02eeeee998cffa94/mmh3-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f1fbb0a99125b1287c6d9747f937dc66621426836d1a2d50d05aecfc81911b57", size = 100078, upload-time = "2026-03-05T15:53:55.234Z" }, + { url = "https://files.pythonhosted.org/packages/27/9a/dd4d5a5fb893e64f71b42b69ecae97dd78db35075412488b24036bc5599c/mmh3-5.2.1-cp310-cp310-win32.whl", hash = "sha256:b4cce60d0223074803c9dbe0721ad3fa51dafe7d462fee4b656a1aa01ee07518", size = 40756, upload-time = "2026-03-05T15:53:56.319Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/0b25889450f8aeffcec840aa73251e853f059c1b72ed1d1c027b956f95f5/mmh3-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f01f044112d43a20be2f13a11683666d87151542ad627fe41a18b9791d2802f", size = 41519, upload-time = "2026-03-05T15:53:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/8fd42e3c526d0bcb1db7f569c0de6729e180860a0495e387a53af33c2043/mmh3-5.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:7501e9be34cb21e72fcfe672aafd0eee65c16ba2afa9dcb5500a587d3a0580f0", size = 39285, upload-time = "2026-03-05T15:53:58.697Z" }, + { url = "https://files.pythonhosted.org/packages/65/d7/3312a59df3c1cdd783f4cf0c4ee8e9decff9c5466937182e4cc7dbbfe6c5/mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450", size = 56082, upload-time = "2026-03-05T15:53:59.702Z" }, + { url = "https://files.pythonhosted.org/packages/61/96/6f617baa098ca0d2989bfec6d28b5719532cd8d8848782662f5b755f657f/mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0", size = 40458, upload-time = "2026-03-05T15:54:01.548Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b4/9cd284bd6062d711e13d26c04d4778ab3f690c1c38a4563e3c767ec8802e/mmh3-5.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0634581290e6714c068f4aa24020acf7880927d1f0084fa753d9799ae9610082", size = 40079, upload-time = "2026-03-05T15:54:02.743Z" }, + { url = "https://files.pythonhosted.org/packages/f6/09/a806334ce1d3d50bf782b95fcee8b3648e1e170327d4bb7b4bad2ad7d956/mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997", size = 97242, upload-time = "2026-03-05T15:54:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/ee/93/723e317dd9e041c4dc4566a2eb53b01ad94de31750e0b834f1643905e97c/mmh3-5.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db0562c5f71d18596dcd45e854cf2eeba27d7543e1a3acdafb7eef728f7fe85d", size = 103082, upload-time = "2026-03-05T15:54:06.387Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/f96121e69cc48696075071531cf574f112e1ffd08059f4bffb41210e6fc5/mmh3-5.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d9f9a3ce559a5267014b04b82956993270f63ec91765e13e9fd73daf2d2738e", size = 106054, upload-time = "2026-03-05T15:54:07.506Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/192b987ec48d0b2aecf8ac285a9b11fbc00030f6b9c694664ae923458dde/mmh3-5.2.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:960b1b3efa39872ac8b6cc3a556edd6fb90ed74f08c9c45e028f1005b26aa55d", size = 112910, upload-time = "2026-03-05T15:54:09.403Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/03e91fd334ed0144b83343a76eb11f17434cd08f746401488cfeafb2d241/mmh3-5.2.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d30b650595fdbe32366b94cb14f30bb2b625e512bd4e1df00611f99dc5c27fd4", size = 120551, upload-time = "2026-03-05T15:54:10.587Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/b89a71d2ff35c3a764d1c066c7313fc62c7cc48fa48a4b3b0304a4a0146f/mmh3-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82f3802bfc4751f420d591c5c864de538b71cea117fce67e4595c2afede08a15", size = 99096, upload-time = "2026-03-05T15:54:11.76Z" }, + { url = "https://files.pythonhosted.org/packages/36/b5/613772c1c6ed5f7b63df55eb131e887cc43720fec392777b95a79d34e640/mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503", size = 98524, upload-time = "2026-03-05T15:54:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/1524566fe8eaf871e4f7bc44095929fcd2620488f402822d848df19d679c/mmh3-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fc78739b5ec6e4fb02301984a3d442a91406e7700efbe305071e7fd1c78278f2", size = 106239, upload-time = "2026-03-05T15:54:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/21adfa7d90a7a697137ad6de33eeff6445420ca55e433a5d4919c79bc3b5/mmh3-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:41aac7002a749f08727cb91babff1daf8deac317c0b1f317adc69be0e6c375d1", size = 109797, upload-time = "2026-03-05T15:54:15.819Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e6/1aacc3a219e1aa62fa65669995d4a3562b35be5200ec03680c7e4bec9676/mmh3-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d8089d853c7963a8ce87fff93e2a67075c0bc08684a08ea6ad13577c38ffc38", size = 97228, upload-time = "2026-03-05T15:54:16.992Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b9/5e4cca8dcccf298add0a27f3c357bc8cf8baf821d35cdc6165e4bd5a48b0/mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728", size = 40751, upload-time = "2026-03-05T15:54:18.714Z" }, + { url = "https://files.pythonhosted.org/packages/72/fc/5b11d49247f499bcda591171e9cf3b6ee422b19e70aa2cef2e0ae65ca3b9/mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a", size = 41517, upload-time = "2026-03-05T15:54:19.764Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/2a511ee8a1c2a527c77726d5231685b72312c5a1a1b7639ad66a9652aa84/mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f", size = 39287, upload-time = "2026-03-05T15:54:20.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, + { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, + { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, + { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, + { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, + { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, + { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, + { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, + { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, + { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, +] + +[[package]] +name = "mplsoccer" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pandas", marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "seaborn", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/b9/b8ae3b1ea8d04ea5c3e2379490e72867f6fd474fbb27d57cce4c2e89afbb/mplsoccer-1.6.0.tar.gz", hash = "sha256:02c2a3c4056b0a4d79564f06639c6bc62550a87db22917cd424d5785fc34a15e", size = 832732, upload-time = "2025-10-26T19:15:52.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ee/e77791bbe673852936a559d314a445bb57a880b2dceb3bc4769da69a6763/mplsoccer-1.6.0-py3-none-any.whl", hash = "sha256:34800fa3b26212dda7d366bbacd863bd2a5723563274288d99ec6737d14501d5", size = 88512, upload-time = "2025-10-26T19:15:50.525Z" }, +] + +[[package]] +name = "mplsoccer" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "12.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "seaborn", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/df/e810b16644a51d3055a0c0942d7f267fc08ae3d313545dcd4ad423ed61b9/mplsoccer-1.6.1.tar.gz", hash = "sha256:021d5369049d3f3f2aecade7b3a94240e0c2b5458835bdca09421a01ad9d0f15", size = 832792, upload-time = "2025-11-01T07:27:06.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/63/8dd3cdf8acac342d5011900c10d2a8238d648b796e0ab45c6603668d1741/mplsoccer-1.6.1-py3-none-any.whl", hash = "sha256:a8311a32290804b5d8d8aa4c117b2ec8d2444cbf72cb06ec40120072ee2da6dd", size = 88512, upload-time = "2025-11-01T07:27:08.247Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/74525ebe3eb5fddcd6735fc03cbea3feeed4122b53bc798ac32d297ac9ae/multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f", size = 77107, upload-time = "2026-01-26T02:46:12.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9a/ce8744e777a74b3050b1bf56be3eed1053b3457302ea055f1ea437200a23/multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358", size = 44943, upload-time = "2026-01-26T02:46:14.016Z" }, + { url = "https://files.pythonhosted.org/packages/83/9c/1d2a283d9c6f31e260cb6c2fccadc3edcf6c4c14ee0929cd2af4d2606dd7/multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5", size = 44603, upload-time = "2026-01-26T02:46:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/87/9d/3b186201671583d8e8d6d79c07481a5aafd0ba7575e3d8566baec80c1e82/multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0", size = 240573, upload-time = "2026-01-26T02:46:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/42/7d/a52f5d4d0754311d1ac78478e34dff88de71259a8585e05ee14e5f877caf/multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8", size = 240106, upload-time = "2026-01-26T02:46:18.432Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/d80118e6c30ff55b7d171bdc5520aad4b9626e657520b8d7c8ca8c2fad12/multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0", size = 219418, upload-time = "2026-01-26T02:46:20.526Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/896e60b3457f194de77c7de64f9acce9f75da0518a5230ce1df534f6747b/multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f", size = 252124, upload-time = "2026-01-26T02:46:22.157Z" }, + { url = "https://files.pythonhosted.org/packages/f4/de/ba6b30447c36a37078d0ba604aa12c1a52887af0c355236ca6e0a9d5286f/multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f", size = 249402, upload-time = "2026-01-26T02:46:23.718Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b2/50a383c96230e432895a2fd3bcfe1b65785899598259d871d5de6b93180c/multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e", size = 240346, upload-time = "2026-01-26T02:46:25.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/37/16d391fd8da544b1489306e38a46785fa41dd0f0ef766837ed7d4676dde0/multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2", size = 237010, upload-time = "2026-01-26T02:46:27.408Z" }, + { url = "https://files.pythonhosted.org/packages/b0/24/3152ee026eda86d5d3e3685182911e6951af7a016579da931080ce6ac9ad/multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8", size = 232018, upload-time = "2026-01-26T02:46:29.941Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1f/48d3c27a72be7fd23a55d8847193c459959bf35a5bb5844530dab00b739b/multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941", size = 241498, upload-time = "2026-01-26T02:46:32.052Z" }, + { url = "https://files.pythonhosted.org/packages/1a/45/413643ae2952d0decdf6c1250f86d08a43e143271441e81027e38d598bd7/multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a", size = 247957, upload-time = "2026-01-26T02:46:33.666Z" }, + { url = "https://files.pythonhosted.org/packages/50/f8/f1d0ac23df15e0470776388bdb261506f63af1f81d28bacb5e262d6e12b6/multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de", size = 241651, upload-time = "2026-01-26T02:46:35.7Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c9/1a2a18f383cf129add66b6c36b75c3911a7ba95cf26cb141482de085cc12/multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5", size = 236371, upload-time = "2026-01-26T02:46:37.37Z" }, + { url = "https://files.pythonhosted.org/packages/bb/aa/77d87e3fca31325b87e0eb72d5fe9a7472dcb51391a42df7ac1f3842f6c0/multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0", size = 41426, upload-time = "2026-01-26T02:46:39.026Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b3/e8863e6a2da15a9d7e98976ff402e871b7352c76566df6c18d0378e0d9cf/multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4", size = 46180, upload-time = "2026-01-26T02:46:40.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/d3/dd4fa951ad5b5fa216bf30054d705683d13405eea7459833d78f31b74c9c/multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9", size = 43231, upload-time = "2026-01-26T02:46:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, + { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/b4/59/a7748ef43446163a93159d82bb270c6c4f3d94c1fcbdd2a29a7e439e74d7/mypy-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0dde5cb375cb94deff0d4b548b993bec52859d1651e073d63a1386d392a95495", size = 13094255, upload-time = "2025-11-28T15:47:14.282Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0b/92ebf5abc83f559a35dcba3bd9227726b04b04178f1e521f38e647b930eb/mypy-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1cf9c59398db1c68a134b0b5354a09a1e124523f00bacd68e553b8bd16ff3299", size = 12161414, upload-time = "2025-11-28T15:45:03.302Z" }, + { url = "https://files.pythonhosted.org/packages/aa/03/19412f0a786722055a52c01b4c5d71e5b5443a89f6bbcdd445408240e217/mypy-1.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3210d87b30e6af9c8faed61be2642fcbe60ef77cec64fa1ef810a630a4cf671c", size = 12756782, upload-time = "2025-11-28T15:46:49.522Z" }, + { url = "https://files.pythonhosted.org/packages/cb/85/395d53c9098b251414b0448cdadcd3277523ff36f5abda6d26ff945dbdb3/mypy-1.19.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2c1101ab41d01303103ab6ef82cbbfedb81c1a060c868fa7cc013d573d37ab5", size = 13503492, upload-time = "2025-11-28T15:48:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/dd/33/1ab1113e3778617ae7aba66b4b537f90512bd279ff65b6c984fb91fbb2d3/mypy-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ea4fd21bb48f0da49e6d3b37ef6bd7e8228b9fe41bbf4d80d9364d11adbd43c", size = 13787703, upload-time = "2025-11-28T15:48:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2d/8b0821b3e0d538de1ad96c86502256c7326274d5cb74e0b373efaada273f/mypy-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:16f76ff3f3fd8137aadf593cb4607d82634fca675e8211ad75c43d86033ee6c6", size = 10049225, upload-time = "2025-11-28T15:45:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "2.2.2.240807" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "types-pytz", version = "2025.2.0.20251108", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/df/0da95bc75c76f1e012e0bc0b76da31faaf4254e94b9870f25e6311145e98/pandas_stubs-2.2.2.240807.tar.gz", hash = "sha256:64a559725a57a449f46225fbafc422520b7410bff9252b661a225b5559192a93", size = 103095, upload-time = "2024-08-07T12:30:54.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/f9/22c91632ea1b4c6165952f677bf9ad95f9ac36ffd7ef3e6450144e6d8b1a/pandas_stubs-2.2.2.240807-py3-none-any.whl", hash = "sha256:893919ad82be4275f0d07bb47a95d08bae580d3fdea308a7acfcb3f02e76186e", size = 157069, upload-time = "2024-08-07T12:30:51.868Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "2.3.3.260113" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "types-pytz", version = "2026.1.1.20260304", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/5d/be23854a73fda69f1dbdda7bc10fbd6f930bd1fa87aaec389f00c901c1e8/pandas_stubs-2.3.3.260113.tar.gz", hash = "sha256:076e3724bcaa73de78932b012ec64b3010463d377fa63116f4e6850643d93800", size = 116131, upload-time = "2026-01-13T22:30:16.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c6/df1fe324248424f77b89371116dab5243db7f052c32cc9fe7442ad9c5f75/pandas_stubs-2.3.3.260113-py3-none-any.whl", hash = "sha256:ec070b5c576e1badf12544ae50385872f0631fc35d99d00dc598c2954ec564d3", size = 168246, upload-time = "2026-01-13T22:30:15.244Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "3.0.0.260204" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/1d/297ff2c7ea50a768a2247621d6451abb2a07c0e9be7ca6d36ebe371658e5/pandas_stubs-3.0.0.260204.tar.gz", hash = "sha256:bf9294b76352effcffa9cb85edf0bed1339a7ec0c30b8e1ac3d66b4228f1fbc3", size = 109383, upload-time = "2026-02-04T15:17:17.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl", hash = "sha256:5ab9e4d55a6e2752e9720828564af40d48c4f709e6a2c69b743014a6fcb6c241", size = 168540, upload-time = "2026-02-04T15:17:15.615Z" }, +] + +[[package]] +name = "parso" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pdoc" +version = "6.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "pygments" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/bc/e14e4f717730fd50b596f659bcc5cada0868537787fa28cd83ad84f67e67/pdoc-6.4.4-py3-none-any.whl", hash = "sha256:a1ca95cfd1c25b10947e50a10ffa261e3269f67c5f520b243d1c8e8302191f56", size = 92519, upload-time = "2021-04-21T11:25:16.001Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" }, + { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" }, + { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" }, + { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" }, + { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "polars" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/dc/56f2a90c79a2cb13f9e956eab6385effe54216ae7a2068b3a6406bae4345/polars-1.36.1.tar.gz", hash = "sha256:12c7616a2305559144711ab73eaa18814f7aa898c522e7645014b68f1432d54c", size = 711993, upload-time = "2025-12-10T01:14:53.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/c6/36a1b874036b49893ecae0ac44a2f63d1a76e6212631a5b2f50a86e0e8af/polars-1.36.1-py3-none-any.whl", hash = "sha256:853c1bbb237add6a5f6d133c15094a9b727d66dd6a4eb91dbb07cdb056b2b8ef", size = 802429, upload-time = "2025-12-10T01:13:53.838Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/df/597c0ef5eb8d761a16d72327846599b57c5d40d7f9e74306fc154aba8c37/polars_runtime_32-1.36.1.tar.gz", hash = "sha256:201c2cfd80ceb5d5cd7b63085b5fd08d6ae6554f922bcb941035e39638528a09", size = 2788751, upload-time = "2025-12-10T01:14:54.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/ea/871129a2d296966c0925b078a9a93c6c5e7facb1c5eebfcd3d5811aeddc1/polars_runtime_32-1.36.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:327b621ca82594f277751f7e23d4b939ebd1be18d54b4cdf7a2f8406cecc18b2", size = 43494311, upload-time = "2025-12-10T01:13:56.096Z" }, + { url = "https://files.pythonhosted.org/packages/d8/76/0038210ad1e526ce5bb2933b13760d6b986b3045eccc1338e661bd656f77/polars_runtime_32-1.36.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ab0d1f23084afee2b97de8c37aa3e02ec3569749ae39571bd89e7a8b11ae9e83", size = 39300602, upload-time = "2025-12-10T01:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/54/1e/2707bee75a780a953a77a2c59829ee90ef55708f02fc4add761c579bf76e/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:899b9ad2e47ceb31eb157f27a09dbc2047efbf4969a923a6b1ba7f0412c3e64c", size = 44511780, upload-time = "2025-12-10T01:14:02.285Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/3fede95feee441be64b4bcb32444679a8fbb7a453a10251583053f6efe52/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d9d077bb9df711bc635a86540df48242bb91975b353e53ef261c6fae6cb0948f", size = 40688448, upload-time = "2025-12-10T01:14:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/05/0f/e629713a72999939b7b4bfdbf030a32794db588b04fdf3dc977dd8ea6c53/polars_runtime_32-1.36.1-cp39-abi3-win_amd64.whl", hash = "sha256:cc17101f28c9a169ff8b5b8d4977a3683cd403621841623825525f440b564cf0", size = 44464898, upload-time = "2025-12-10T01:14:08.296Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d8/a12e6aa14f63784cead437083319ec7cece0d5bb9a5bfe7678cc6578b52a/polars_runtime_32-1.36.1-cp39-abi3-win_arm64.whl", hash = "sha256:809e73857be71250141225ddd5d2b30c97e6340aeaa0d445f930e01bef6888dc", size = 39798896, upload-time = "2025-12-10T01:14:11.568Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "identify", version = "2.6.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "nodeenv", marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "virtualenv", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "identify", version = "2.6.17", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "nodeenv", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "virtualenv", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" }, + { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" }, + { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" }, + { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" }, + { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d9/110de31880016e2afc52d8580b397dbe47615defbf09ca8cf55f56c62165/pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26", size = 31196837, upload-time = "2025-07-18T00:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/5f/c1c1997613abf24fceb087e79432d24c19bc6f7259cab57c2c8e5e545fab/pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79", size = 32659470, upload-time = "2025-07-18T00:54:38.329Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ed/b1589a777816ee33ba123ba1e4f8f02243a844fed0deec97bde9fb21a5cf/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb", size = 41055619, upload-time = "2025-07-18T00:54:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/44/28/b6672962639e85dc0ac36f71ab3a8f5f38e01b51343d7aa372a6b56fa3f3/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51", size = 42733488, upload-time = "2025-07-18T00:54:47.132Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/de02c3614874b9089c94eac093f90ca5dfa6d5afe45de3ba847fd950fdf1/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a", size = 43329159, upload-time = "2025-07-18T00:54:51.686Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3e/99473332ac40278f196e105ce30b79ab8affab12f6194802f2593d6b0be2/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594", size = 45050567, upload-time = "2025-07-18T00:54:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/c372ef60593d713e8bfbb7e0c743501605f0ad00719146dc075faf11172b/pyarrow-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634", size = 26217959, upload-time = "2025-07-18T00:55:00.482Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload-time = "2025-07-18T00:55:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload-time = "2025-07-18T00:55:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload-time = "2025-07-18T00:55:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload-time = "2025-07-18T00:55:16.301Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload-time = "2025-07-18T00:55:23.82Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload-time = "2025-07-18T00:55:28.231Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload-time = "2025-07-18T00:55:32.122Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" }, + { url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" }, + { url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cc/ce4939f4b316457a083dc5718b3982801e8c33f921b3c98e7a93b7c7491f/pyarrow-21.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a7f6524e3747e35f80744537c78e7302cd41deee8baa668d56d55f77d9c464b3", size = 31211248, upload-time = "2025-07-18T00:56:59.7Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c2/7a860931420d73985e2f340f06516b21740c15b28d24a0e99a900bb27d2b/pyarrow-21.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:203003786c9fd253ebcafa44b03c06983c9c8d06c3145e37f1b76a1f317aeae1", size = 32676896, upload-time = "2025-07-18T00:57:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/197f989b9a75e59b4ca0db6a13c56f19a0ad8a298c68da9cc28145e0bb97/pyarrow-21.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b4d97e297741796fead24867a8dabf86c87e4584ccc03167e4a811f50fdf74d", size = 41067862, upload-time = "2025-07-18T00:57:07.587Z" }, + { url = "https://files.pythonhosted.org/packages/fa/82/6ecfa89487b35aa21accb014b64e0a6b814cc860d5e3170287bf5135c7d8/pyarrow-21.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:898afce396b80fdda05e3086b4256f8677c671f7b1d27a6976fa011d3fd0a86e", size = 42747508, upload-time = "2025-07-18T00:57:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b7/ba252f399bbf3addc731e8643c05532cf32e74cebb5e32f8f7409bc243cf/pyarrow-21.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:067c66ca29aaedae08218569a114e413b26e742171f526e828e1064fcdec13f4", size = 43345293, upload-time = "2025-07-18T00:57:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/a20819795bd702b9486f536a8eeb70a6aa64046fce32071c19ec8230dbaa/pyarrow-21.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0c4e75d13eb76295a49e0ea056eb18dbd87d81450bfeb8afa19a7e5a75ae2ad7", size = 45060670, upload-time = "2025-07-18T00:57:24.477Z" }, + { url = "https://files.pythonhosted.org/packages/10/15/6b30e77872012bbfe8265d42a01d5b3c17ef0ac0f2fae531ad91b6a6c02e/pyarrow-21.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdc4c17afda4dab2a9c0b79148a43a7f4e1094916b3e18d8975bfd6d6d52241f", size = 26227521, upload-time = "2025-07-18T00:57:29.119Z" }, +] + +[[package]] +name = "pyarrow" +version = "22.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/9b/cb3f7e0a345353def531ca879053e9ef6b9f38ed91aebcf68b09ba54dec0/pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88", size = 34223968, upload-time = "2025-10-24T10:03:31.21Z" }, + { url = "https://files.pythonhosted.org/packages/6c/41/3184b8192a120306270c5307f105b70320fdaa592c99843c5ef78aaefdcf/pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace", size = 35942085, upload-time = "2025-10-24T10:03:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3d/a1eab2f6f08001f9fb714b8ed5cfb045e2fe3e3e3c0c221f2c9ed1e6d67d/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b9d71701ce97c95480fecb0039ec5bb889e75f110da72005743451339262f4ce", size = 44964613, upload-time = "2025-10-24T10:03:46.516Z" }, + { url = "https://files.pythonhosted.org/packages/46/46/a1d9c24baf21cfd9ce994ac820a24608decf2710521b29223d4334985127/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:710624ab925dc2b05a6229d47f6f0dac1c1155e6ed559be7109f684eba048a48", size = 47627059, upload-time = "2025-10-24T10:03:55.353Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/f711acb13075c1391fd54bc17e078587672c575f8de2a6e62509af026dcf/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f963ba8c3b0199f9d6b794c90ec77545e05eadc83973897a4523c9e8d84e9340", size = 47947043, upload-time = "2025-10-24T10:04:05.408Z" }, + { url = "https://files.pythonhosted.org/packages/4e/70/1f3180dd7c2eab35c2aca2b29ace6c519f827dcd4cfeb8e0dca41612cf7a/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd0d42297ace400d8febe55f13fdf46e86754842b860c978dfec16f081e5c653", size = 50206505, upload-time = "2025-10-24T10:04:15.786Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/fea6578112c8c60ffde55883a571e4c4c6bc7049f119d6b09333b5cc6f73/pyarrow-22.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84", size = 28101641, upload-time = "2025-10-24T10:04:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a", size = 34272022, upload-time = "2025-10-24T10:04:28.973Z" }, + { url = "https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e", size = 35995834, upload-time = "2025-10-24T10:04:35.467Z" }, + { url = "https://files.pythonhosted.org/packages/50/8d/281f0f9b9376d4b7f146913b26fac0aa2829cd1ee7e997f53a27411bbb92/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215", size = 45030348, upload-time = "2025-10-24T10:04:43.366Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d", size = 47699480, upload-time = "2025-10-24T10:04:51.486Z" }, + { url = "https://files.pythonhosted.org/packages/95/e1/9dbe4c465c3365959d183e6345d0a8d1dc5b02ca3f8db4760b3bc834cf25/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8", size = 48011148, upload-time = "2025-10-24T10:04:59.585Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/7caf5d21930061444c3cf4fa7535c82faf5263e22ce43af7c2759ceb5b8b/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016", size = 50276964, upload-time = "2025-10-24T10:05:08.175Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c", size = 28106517, upload-time = "2025-10-24T10:05:14.314Z" }, + { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, + { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", size = 34204629, upload-time = "2025-10-24T10:06:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", size = 35985783, upload-time = "2025-10-24T10:06:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", size = 45020999, upload-time = "2025-10-24T10:06:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", size = 47724601, upload-time = "2025-10-24T10:06:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", size = 48001050, upload-time = "2025-10-24T10:06:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", size = 50307877, upload-time = "2025-10-24T10:07:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", size = 27977099, upload-time = "2025-10-24T10:08:07.259Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", size = 34336685, upload-time = "2025-10-24T10:07:11.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", size = 36032158, upload-time = "2025-10-24T10:07:18.626Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", size = 44892060, upload-time = "2025-10-24T10:07:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", size = 47504395, upload-time = "2025-10-24T10:07:34.09Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/0fa4d28a8edb42b0a7144edd20befd04173ac79819547216f8a9f36f9e50/pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d", size = 34224062, upload-time = "2025-10-24T10:08:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/7a719076b3c1be0acef56a07220c586f25cd24de0e3f3102b438d18ae5df/pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9", size = 35990057, upload-time = "2025-10-24T10:08:21.842Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/359ed54c93b47fb6fe30ed16cdf50e3f0e8b9ccfb11b86218c3619ae50a8/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7", size = 45068002, upload-time = "2025-10-24T10:08:29.034Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/4945896cc8638536ee787a3bd6ce7cec8ec9acf452d78ec39ab328efa0a1/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde", size = 47737765, upload-time = "2025-10-24T10:08:38.559Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/7cb7edeb2abfaa1f79b5d5eb89432356155c8426f75d3753cbcb9592c0fd/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc", size = 48048139, upload-time = "2025-10-24T10:08:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/546baa7c48185f5e9d6e59277c4b19f30f48c94d9dd938c2a80d4d6b067c/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0", size = 50314244, upload-time = "2025-10-24T10:08:55.771Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/755ff2d145aafec8d347bf18f95e4e81c00127f06d080135dfc86aea417c/pyarrow-22.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730", size = 28757501, upload-time = "2025-10-24T10:09:59.891Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d2/237d75ac28ced3147912954e3c1a174df43a95f4f88e467809118a8165e0/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2", size = 34355506, upload-time = "2025-10-24T10:09:02.953Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/733dfffe6d3069740f98e57ff81007809067d68626c5faef293434d11bd6/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70", size = 36047312, upload-time = "2025-10-24T10:09:10.334Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2b/29d6e3782dc1f299727462c1543af357a0f2c1d3c160ce199950d9ca51eb/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754", size = 45081609, upload-time = "2025-10-24T10:09:18.61Z" }, + { url = "https://files.pythonhosted.org/packages/8d/42/aa9355ecc05997915af1b7b947a7f66c02dcaa927f3203b87871c114ba10/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91", size = 47703663, upload-time = "2025-10-24T10:09:27.369Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/45abedde480168e83a1de005b7b7043fd553321c1e8c5a9a114425f64842/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c", size = 48066543, upload-time = "2025-10-24T10:09:34.908Z" }, + { url = "https://files.pythonhosted.org/packages/84/e9/7878940a5b072e4f3bf998770acafeae13b267f9893af5f6d4ab3904b67e/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80", size = 50288838, upload-time = "2025-10-24T10:09:44.394Z" }, + { url = "https://files.pythonhosted.org/packages/7b/03/f335d6c52b4a4761bcc83499789a1e2e16d9d201a58c327a9b5cc9a41bd9/pyarrow-22.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae", size = 29185594, upload-time = "2025-10-24T10:09:53.111Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" }, + { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" }, + { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyiceberg" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cachetools", marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mmh3", version = "5.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "pyparsing", marker = "python_full_version < '3.10'" }, + { name = "pyroaring", marker = "python_full_version < '3.10'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "sortedcontainers", marker = "python_full_version < '3.10'" }, + { name = "strictyaml", marker = "python_full_version < '3.10'" }, + { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/0e/90e61c38504f4fbd5ed79631f85da7d5ea5e5bf997bdeaa65b28ebf04cab/pyiceberg-0.10.0.tar.gz", hash = "sha256:2525afa5e7e5fc4e72b291f8e1cc219e982d2bda5ff17e62cd05b8d91c4139f5", size = 842633, upload-time = "2025-09-11T14:59:34.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/82/5dbcf36c13ddf528a6c4ba7f75ace2766859870e7e166096c73c8e63c457/pyiceberg-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:03a4f208f0c59c040d2a6ff51b952479358810aac28c5271de3fd1fa425f063c", size = 553282, upload-time = "2025-09-11T14:58:58.667Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a0/ca556da105ce64269e46977204777f6d5e1d8595f711f6d0edb3bbc58eff/pyiceberg-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6924f496f9a6e36f6530ce66483486f71d3cb4c08512d5aeb21095a9aa22d4b7", size = 550649, upload-time = "2025-09-11T14:59:00.931Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6c/def488007282c3d9ece0ca68fd6c4c7f42c500c0125cba3b8767731bfe5e/pyiceberg-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e549ca852233b1aa20f2af1a8f9276b4a064c2515be0d73d36f28282502b8728", size = 906149, upload-time = "2025-09-11T14:59:03.47Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a3/08431f7c48ce3f236b1705586c73797eb03b86ca848b12adbbc2b60ed65b/pyiceberg-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14a33da7eb579a02fc559d2e7b703a8133e81bc206bf11cb76ece9333ddfb7f3", size = 903762, upload-time = "2025-09-11T14:59:05.416Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bf/5c1c978dad3e4c52c48ab40749f1e49ee0dba26872863a0a3b4385cf81d3/pyiceberg-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:4dfafd712fec5c3776fafbb444784e20256b8674cbe243487f8cd7f99a6e8836", size = 549059, upload-time = "2025-09-11T14:59:06.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/62/b6f7bed760d0896958d046ca3c188fd15467c6502bcc2dc301ac0554c1ce/pyiceberg-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c799c9149e06ef9ece22945d5c198ffc69f5c04b314b59a43c2d4c1bb9ade84", size = 591127, upload-time = "2025-09-11T14:59:08.72Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b2/294c74e70c68744a8246924fee350095cc46f97f81d1e37125011d8e1bcb/pyiceberg-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a8c7070fe1262f50694b12241b5373ee89c8aededda82ef325cb14e5a95cc461", size = 587041, upload-time = "2025-09-11T14:59:10.643Z" }, + { url = "https://files.pythonhosted.org/packages/7a/2f/9a9f0a01f0dae2cefc024a2bd84a00ff2a5d8d952f37053c46523c1dd7a6/pyiceberg-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0d1a4896f546b1e115ece4212dd02b383eeb3c7ff5c072624b15f531b776f36", size = 1135929, upload-time = "2025-09-11T14:59:12.164Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c2/51deddeec916d44a04cc26053179b560ffceba72e4561b6cf58a64aea209/pyiceberg-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b0ef2f1880dd7549cc54ccb1a25f61ad5329e079cba372b4c239b0012aecac6", size = 1131851, upload-time = "2025-09-11T14:59:13.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e9cf3fa56d67306ba29352d56152907a91ca29eabc1a30d3177cee0d1418/pyiceberg-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:2127c795e451b971bd3f55cbda2d2c8200182bec3476e590e4a3453e60efda3c", size = 583472, upload-time = "2025-09-11T14:59:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/03/61/f5042dd09cb91deed908a39acd5012f1ac6910ddf84ada889751732f0df8/pyiceberg-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64cad9d1db08192605875a872152cbcaca147ea486cfa94773fa5f4f65d78a23", size = 629281, upload-time = "2025-09-11T14:59:17.585Z" }, + { url = "https://files.pythonhosted.org/packages/8e/50/960f7239eedd4b1bab2a611f5e100fffc138549c1213760a57cd24a5bac1/pyiceberg-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3e12cf585318f0f48d31a77b4149e0e5b4c41e03a24aa8612e060f20ff41eb10", size = 623424, upload-time = "2025-09-11T14:59:19.045Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2b/756a74c80db6edd82c8d3f23c3ae13e7d6620300b87ef792c2a4d3935b30/pyiceberg-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6979dd741cee263c1235595f71888c73365f2725697411027c4bd81046db3294", size = 1377048, upload-time = "2025-09-11T14:59:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/9c18cb4ddc7d371db63714abb2f5e8414bc7a4d63f474644a2aea2933fe6/pyiceberg-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:13fd03ec3da6eb4d3b55ff94b647946a7749bede5d743c75b39deaad26421200", size = 1369921, upload-time = "2025-09-11T14:59:22.134Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b3/c012dc6b5bc3d0a84821936789c753f5c44aec619b64fbcf7f90038d172e/pyiceberg-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:33367c84bcb0a2fbbe54cbbfe062691ab93b91a2e3d319bb546ec5b9b45b6057", size = 617722, upload-time = "2025-09-11T14:59:23.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/65/8a93fe2d72a99700da6372d68c78c551e1fdb8ce441b570d724506faaf93/pyiceberg-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14cb3a5186e64f2ab37bc69cd7d1b32b25f416c87f9dadbcaa4f8e21b6c4e7b1", size = 516206, upload-time = "2025-09-11T14:59:25.224Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e3/554a2130a6e137a6621fd6c064937354a8bf3406cf3f405a23a5a356f4e2/pyiceberg-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9631d892f0977fbaef0498f088cb8535cd6b933606946dcce214a9a342d9c009", size = 514863, upload-time = "2025-09-11T14:59:27.522Z" }, + { url = "https://files.pythonhosted.org/packages/a9/87/bdf9f0751e7501ac247fe8a49a65d68bf84e671242a1c6ec15a3337bcfc3/pyiceberg-0.10.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79016a97ec70a11e2920791fe2620b001bc5b657380d3d3ddf9f6a48af209615", size = 692516, upload-time = "2025-09-11T14:59:29.123Z" }, + { url = "https://files.pythonhosted.org/packages/34/de/c6a601fc18bce3d570393be3842dd2174a8f0e8facb4bee93e553e2e502e/pyiceberg-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912128d6b70313002b5418096c444afcde3d541e53b5e6a6b4df177531ac5686", size = 691178, upload-time = "2025-09-11T14:59:30.459Z" }, + { url = "https://files.pythonhosted.org/packages/31/df/10b7d7da19efbbbbbf2f5452d8529b83853b0b82b871303702e693ef6994/pyiceberg-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:0d76efec0409536bf48146961abc2b94e4001c647d348da8ea0f8ccca6504d1f", size = 515431, upload-time = "2025-09-11T14:59:31.742Z" }, +] + +[[package]] +name = "pyiceberg" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cachetools", marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mmh3", version = "5.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "pyparsing", marker = "python_full_version >= '3.10'" }, + { name = "pyroaring", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "strictyaml", marker = "python_full_version >= '3.10'" }, + { name = "tenacity", version = "9.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "zstandard", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/f0/7616676603fdbd05ab97816337a9b31be08a5f9e1ffd636260812b217e0f/pyiceberg-0.11.1.tar.gz", hash = "sha256:366fe0d5a74e3cf1d4e7cbf3c49e308da60e7835ea268667be9185388f05d7a5", size = 1076075, upload-time = "2026-03-03T00:10:27.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/40/ea9cc312ea8ad76d94c57f921be19256c3d07354056c68e64601ff43f98d/pyiceberg-0.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89c347170a691e3b8d072519f36790b9c19a2263ec0af0144c873994ecfe55eb", size = 532405, upload-time = "2026-03-03T00:09:49.364Z" }, + { url = "https://files.pythonhosted.org/packages/97/e9/fbcd52d19aa2a362ba7fd971b1721c57f8c0fd37b5bb418ac283dde9637e/pyiceberg-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:138983e96a5c473aae1e1cd6143aacf02ccc9bbbf6180f15fa2588f99531188b", size = 533282, upload-time = "2026-03-03T00:09:50.712Z" }, + { url = "https://files.pythonhosted.org/packages/1d/43/a0722863f821e96eed8ff5753a7146dd5b1537668d6684ac650a48647669/pyiceberg-0.11.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e08c268e12d54ea629a1f123bf7cb9587bdd57fd7bcdac22394dc6272a18668f", size = 708618, upload-time = "2026-03-03T00:09:52.427Z" }, + { url = "https://files.pythonhosted.org/packages/72/76/3f4aa7f8fe99292b8074720f5726040f6c7528209666444f1d6166c17aeb/pyiceberg-0.11.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23ed91f8d2fa75a109708338f80a7addd2f37c93a8b0e6c9d220f1427939b81f", size = 705581, upload-time = "2026-03-03T00:09:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/27/12/85543a838b2b3f4a74dfd8de470f6d88f6ba5d287231b6ea4e8765df04f6/pyiceberg-0.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4b91f1b1bac135c077326d30a1876f917e6c36b844a2e329b148fb9cb7aa660", size = 704197, upload-time = "2026-03-03T00:09:55.809Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/7a37d331722a4cfda0e782ec583aa28af412f8564ac937733eac440aa7b0/pyiceberg-0.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7770e7bdee34549e8baf28b8626e6552de7894d869465a7c95fd9af33b4cbeb0", size = 703982, upload-time = "2026-03-03T00:09:57.098Z" }, + { url = "https://files.pythonhosted.org/packages/65/16/61c2af5ab30f61f3b523346d109bc52b035bb26ecdfa58524c63fac190c4/pyiceberg-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:ccac408873f65b8954858ef08d4b098841301e232afc1b14e3eada489d303a7f", size = 530720, upload-time = "2026-03-03T00:09:58.548Z" }, + { url = "https://files.pythonhosted.org/packages/62/f7/3b7fee2ecc021f0526f23ef4ae5dcc8e0ed26062c35890ad25d39c53fb3b/pyiceberg-0.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba98d6a41ec0b7c81dd85d764f15653d6abbbbd69d92630677c43f92dd50d924", size = 532406, upload-time = "2026-03-03T00:09:59.647Z" }, + { url = "https://files.pythonhosted.org/packages/94/25/324030b13d91b7b564fb7342bf3fcdbf76eed2672964b273b156bf84d6e5/pyiceberg-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6400774e6820760eb6c322f6feace43fe7267deb9f8d508f10bf258887a9c4d5", size = 533368, upload-time = "2026-03-03T00:10:01.264Z" }, + { url = "https://files.pythonhosted.org/packages/1c/83/6a43d06a079292c4fc7815b4de3e90a05ded90031c35d0a1b037659f722b/pyiceberg-0.11.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1663d79fc8400903992c63f79b3908b9298c623138e8929bf36c559231e082d3", size = 722886, upload-time = "2026-03-03T00:10:02.558Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5c/53807036b63bb810f2c56b4b5576e79b721ba93f1c16bd0dd49ecfe41055/pyiceberg-0.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:856c7fca5ed780ed44f60bceb92d6b311ebc008a2249415b8f6045201d4f5530", size = 721212, upload-time = "2026-03-03T00:10:03.694Z" }, + { url = "https://files.pythonhosted.org/packages/db/11/65e25d6016e3844c516c9f04041853115711f64af1ee184a2320c9ceab4c/pyiceberg-0.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf94756fb6a822d20a5a64f44840e6633ebf8b1deb3ce01057bff1cc03b01c2", size = 717978, upload-time = "2026-03-03T00:10:05.011Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/33b4ea9dc7f0c496900f5fc6da79e8587e94b88e2244ff02b786016cc649/pyiceberg-0.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8bd52c1891ae74cee21a4ebe8325953310a2e0af5352d70f47f5461422fcce2d", size = 718747, upload-time = "2026-03-03T00:10:06.269Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/5e133c435efc577afea5be303d7123264a5176a3ce1e2d3dc3a691049eaf/pyiceberg-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:65a7ad892a570045b0de2db6af17119162880aebc05a0c125ce2db7dab36f17e", size = 531081, upload-time = "2026-03-03T00:10:07.352Z" }, + { url = "https://files.pythonhosted.org/packages/8f/84/a140466b7e0841207e6b77042e03d4ab3a4f9d47e00f0bbbcc5420792bbb/pyiceberg-0.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd423b8ee2f75fc9db09158875abe5e2c952a26ae5e521c3265ab2f9d3511ddf", size = 532981, upload-time = "2026-03-03T00:10:08.906Z" }, + { url = "https://files.pythonhosted.org/packages/17/10/6bedd784010f707680ffd0606d4d11394cf915f4f9f54ae16e8007e00ad4/pyiceberg-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e273242cdca56029af694d7ce18075d47a74d034326d663ff6dd2655a6f44825", size = 533188, upload-time = "2026-03-03T00:10:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a3/79db617c3cffc963efa8a332707079d3f22fd58067b31a208d358dd89b39/pyiceberg-0.11.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b347d3cc8510f8fbe191956fcda7da372ebb3302789acefca08e352345959003", size = 729546, upload-time = "2026-03-03T00:10:11.413Z" }, + { url = "https://files.pythonhosted.org/packages/06/64/acc11d230c33817bced80d9d947bb49e7bb3a429d76d906523e3df86faf8/pyiceberg-0.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba3a35b4648694783aeae5b77c235a57191c8b1b375c8602b03ae56a6cf4fe7", size = 730263, upload-time = "2026-03-03T00:10:13.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1a/fb067d5150c7309fbf5dd126c648a6afed6259e7bc924ba3c65d0f87a333/pyiceberg-0.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0f958cbca18d05846e3081dfff8575e73d45595441d659847479656dc76f91d", size = 724064, upload-time = "2026-03-03T00:10:14.55Z" }, + { url = "https://files.pythonhosted.org/packages/c1/71/103fdba5b144d55f3bb07347893737cc1d8fd71308108a77b7817c92c544/pyiceberg-0.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c62636a1e9d8a1fc74ffb70383939b9cd93f2c9ee8e12015a50dd75c98a989e", size = 727239, upload-time = "2026-03-03T00:10:16.204Z" }, + { url = "https://files.pythonhosted.org/packages/18/c3/4db64429304c58c039f8e842cd37a9a1c472f596c2868ed2a5d2907b17ed/pyiceberg-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d6b6f0c1e7dd8357f1ba56524bfc870d04ad3c00979db291784a7145497ad3b", size = 531309, upload-time = "2026-03-03T00:10:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/4c/a122d80d98cb6125d87024681263406433f0c25c699d503f5633521e6809/pyiceberg-0.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7ec5db19feab98a31fcd5caccf4a9a4e83f96933d1ca393ba7aea665710c2bb", size = 532644, upload-time = "2026-03-03T00:10:18.574Z" }, + { url = "https://files.pythonhosted.org/packages/10/94/9a8fa5fc580e6dccd34bbbf51e7658cd7b49540e2458783addeff5e22a91/pyiceberg-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cec0616d2ba6e7dda6327089a2f34ec723aa9ac2c389857ef0b83f65fb135dd6", size = 532787, upload-time = "2026-03-03T00:10:19.656Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ab/ab7c88828bc17d77dbbc5a765419dfec2135629e1d74cdd0762cd38ad867/pyiceberg-0.11.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddb360da76c62c7c23ec3da40e1af48e6712a563905fea2d1a8911ff7a3b6c4d", size = 722202, upload-time = "2026-03-03T00:10:21.012Z" }, + { url = "https://files.pythonhosted.org/packages/df/38/079cf1c0bf86da315472a926eec0dba10135f43374a2e267336eb98d8c76/pyiceberg-0.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d8790f420ebc484236017edba59182cf2a21bd3e4224a0bd0760a9c7268e96a", size = 724037, upload-time = "2026-03-03T00:10:22.176Z" }, + { url = "https://files.pythonhosted.org/packages/08/6b/08eaef477debb110438d943ef3f5985096f660ccb735d6344701cbd075a9/pyiceberg-0.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae27ba4d37925d5b2cff192acaa70c8bb114d632bbc527cc91fea0370702b866", size = 716035, upload-time = "2026-03-03T00:10:23.789Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/7671d6a630ab1d85c6e7ca8ddf438dc63a0b0dd183bc4be69bf25c0fa5f6/pyiceberg-0.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db66a4e0fdfbf4090631d59c3f65e960d9a5561e9259f6f3993cbe91e396837e", size = 720887, upload-time = "2026-03-03T00:10:24.824Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/5c8ad37807efaedb14b20f01f36462684468c80da5b74f4018fb4c1804b5/pyiceberg-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb3a0a3e630ee89758eb96b39b456f4697732351fb0c080e9498ea578f9b71f9", size = 530923, upload-time = "2026-03-03T00:10:26.196Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyroaring" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/71/134bcaf93d8734051ecbc164dabe695645849249e0fb24209b2dd88e8147/pyroaring-1.0.4.tar.gz", hash = "sha256:99d4217bdfeedc91b82efcec940175a9f9a9137c6476faf7ce5d9c9dd889c8e6", size = 189155, upload-time = "2026-03-19T13:57:27.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/b3/5a910bcad7295a7198619540a4d879e447e5e0bf391a18f26e4f6bbd86d0/pyroaring-1.0.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1c5f78aeccf2ce4580cd373f50c20b3e6279a594d6fb827266dbceede9dd8c92", size = 320196, upload-time = "2026-03-19T13:55:31.771Z" }, + { url = "https://files.pythonhosted.org/packages/1f/66/e9d05f2e6d496d60cc7264a64d73d342bd099f164a7cdf2d57386bed4cce/pyroaring-1.0.4-cp310-cp310-macosx_14_0_universal2.whl", hash = "sha256:c0e7768e2143b842f5833857bbcd0ba28b0022028654ca5be395b7320b548460", size = 681634, upload-time = "2026-03-19T13:55:33.049Z" }, + { url = "https://files.pythonhosted.org/packages/6d/52/4e3f4dfd44edb425e43d4a4400308c564750b7f538fb625ca74c05e334fd/pyroaring-1.0.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7497ea5124523bae7c05726e38faa9fe2f6e64c2423f96f0e4a00078d8c1b9db", size = 360359, upload-time = "2026-03-19T13:55:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1e/31e5ea70966af4af507efd1c44240df5f09db73b6a04bdcb582377b9d870/pyroaring-1.0.4-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:827e7ecfa57a0f6e1cfd2449eabbc04fabcc16e4b3ad0f59f7a2d08f5d2fd063", size = 1885434, upload-time = "2026-03-19T13:55:35.668Z" }, + { url = "https://files.pythonhosted.org/packages/81/e1/98bc17b321e6c838edb64c166e8661341b05539bde815e0198a34f56ccba/pyroaring-1.0.4-cp310-cp310-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d3125ddc6e9e9dd58663db2db67c5ef12ac76d7a7e1882cf82581cd2307ac82b", size = 1751735, upload-time = "2026-03-19T13:55:37.194Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/a7d94c948450ab8f63a400d1f5cad61c2135c2b52d060865075027b296db/pyroaring-1.0.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6633a7a91df7aea8340e348408b4eb92a2cd89eae0f99e9b42b1d6eb6a1c9673", size = 2078662, upload-time = "2026-03-19T13:55:38.553Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e9/e3da53c33de08e5be4e6fd5f6444d490bb631d0b0c13bc5f753eeab5c376/pyroaring-1.0.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fcee7f9cd9dcce96e057ae78b7deef0eced728b7452dcf2781b366bd69db3432", size = 2809536, upload-time = "2026-03-19T13:55:40.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/2249294290890dfdbb9d8537e5b322cd3386f716f62dafcccde1aa33edd3/pyroaring-1.0.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dfabce5b120dd50dc46d11066ec111732d19a4f99e5a7720271dbab5b13bb692", size = 2610604, upload-time = "2026-03-19T13:55:41.485Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/368c5b35af66fea64bf4fcb461d55857de4ff484aa3f4ff55ce2f1c2e24e/pyroaring-1.0.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a92af0bf8ad040b513ef8867fda2669708a1b81325295c55cb4e8cc3a835321a", size = 3052109, upload-time = "2026-03-19T13:55:43.193Z" }, + { url = "https://files.pythonhosted.org/packages/7c/79/556ac6b249e2c73dc2fb224baab9ecb497217d711b848c8a9a42a5b5c921/pyroaring-1.0.4-cp310-cp310-win32.whl", hash = "sha256:49a354fdd9359a11c2da5a70d5c202ee339a4328d2c57679acf642e577202b87", size = 202780, upload-time = "2026-03-19T13:55:44.817Z" }, + { url = "https://files.pythonhosted.org/packages/31/ce/d68e2485e3b87e650535be76ed6344b663f41ad723d11a60405e375c8d7a/pyroaring-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:387001bcaa1653c6770eb8dd88f6427cbcef882c2ab8a812949dd8d98d09d545", size = 252209, upload-time = "2026-03-19T13:55:46.087Z" }, + { url = "https://files.pythonhosted.org/packages/14/65/1c29ad4ac8986c0f89ed76ff3ca5c83ca8aeed268594796dec25554bb3f5/pyroaring-1.0.4-cp310-cp310-win_arm64.whl", hash = "sha256:a7553a23a806033ee93f5bd50f7c963d713a26ebecd55f173d24404a8673317d", size = 216508, upload-time = "2026-03-19T13:55:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/84/1c/f96ef38332d29015ef5285c0f6117b5644ccd9930334038740aed1df0a09/pyroaring-1.0.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8e1b2a9f6f6c08e971098867186bfd23fd08afd899db318cc9ed602913b1fc54", size = 319867, upload-time = "2026-03-19T13:55:48.492Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a5/0759a5b55fe4ea0ef322a134117dc2a706ac76e6dcd1706b1de4c5af66a3/pyroaring-1.0.4-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:8a202294c7bfd19c2d08e95e03e486859b351a6f1daf0d69197bd8d355d4044a", size = 680818, upload-time = "2026-03-19T13:55:49.696Z" }, + { url = "https://files.pythonhosted.org/packages/7f/47/f681a3fe58f1ca2712c33b1003e40b3716d0eab267abf8e6ae57b19c9245/pyroaring-1.0.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8157345c01f633f759fc47133a076d3307f85d0246dd17986127fd1e2c349d0", size = 359867, upload-time = "2026-03-19T13:55:51.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/6f/bdcf4b8ba51f921bc5184feb07985dcb32b0aacb78d7003d153e0883afb1/pyroaring-1.0.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:969c24580c9c16daa8e36eb76729d8c59c76250890dc6acdae9adb4d41107c4b", size = 1951858, upload-time = "2026-03-19T13:55:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ac/40/3475ba1a455df6ff617e9babbe998b8973483266384cd5ff4072914228da/pyroaring-1.0.4-cp311-cp311-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2984402d7c003923dc2e9b3e05676417f1de66ec31e4988d4b54c0a6008fe269", size = 1797084, upload-time = "2026-03-19T13:55:53.685Z" }, + { url = "https://files.pythonhosted.org/packages/50/5a/e4426481a29099095dd56b1685a78039456f36cc4afedd55a09b9c137ac3/pyroaring-1.0.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d85f85aa2f56a5dde9c8d97cbfaf80dfe9c7212171f7f263043d0800b44cee3a", size = 2145587, upload-time = "2026-03-19T13:55:55.026Z" }, + { url = "https://files.pythonhosted.org/packages/b8/05/8d14ebe27b888433b61f5a9af8e99b368e58370102449299fecea4501d3a/pyroaring-1.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b69e8799062acf859b13f5369c0976a6033211e72c51f8b7bc817925cab193f", size = 2875499, upload-time = "2026-03-19T13:55:56.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/4fd95a78f6de7dd5c39db8c34905e9fc7a538218abcec76a141d425779e7/pyroaring-1.0.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0333b18734475c531977e7a9027fc5753d7cac836f2475198f630ecefbbfd764", size = 2667086, upload-time = "2026-03-19T13:55:58.431Z" }, + { url = "https://files.pythonhosted.org/packages/43/ff/53af720a9b65858344dcd0765f57df9900ce5c1020038d33d2bbff78203f/pyroaring-1.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0736ee92f8781570d5be22a4f43238937676518a18487b1efafca67a63a89099", size = 3116570, upload-time = "2026-03-19T13:55:59.683Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b7/f278f468acea40f8d3989c803cbb40ee222a86892abdacfe1e882788e292/pyroaring-1.0.4-cp311-cp311-win32.whl", hash = "sha256:76bdac16f535154322ca734cf51d96fa8f8d5a2f8d009a24affba0036f74f677", size = 201943, upload-time = "2026-03-19T13:56:00.905Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4567fa392b4cc5407471b0c1aa8263674dd0611cd51012daf58ada694d4e/pyroaring-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:910c177c3ff77db0d306119963583be85a98870d396472b5a0153babc8b7fcf7", size = 253022, upload-time = "2026-03-19T13:56:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/1676f27f95ebadfa3a221c146ee941db0e91dcd08d1ee21c45106f9e329a/pyroaring-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:c2e909a436ef22b9ba9f7dbc0e88fb684ced3bc6d5192b8315fe5ba180796b34", size = 216497, upload-time = "2026-03-19T13:56:03.547Z" }, + { url = "https://files.pythonhosted.org/packages/89/a7/9f4977405d3a3fa02cf575951f03a9cda4c01efbe27a19230addee06acc2/pyroaring-1.0.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:924ee997ff1a0f2a184e39e153e9f77e0b928fe908d0aef63d03204c3ed90586", size = 322060, upload-time = "2026-03-19T13:56:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e9/32fd7125aea82a3d1d29b755edbc7bb531907638c68a5bcc767d20c2be4a/pyroaring-1.0.4-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b7a527279cc378e893a543a2271d71321b57d21733915a5a14e587532e29265a", size = 685716, upload-time = "2026-03-19T13:56:06.431Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/6b78bd9d743c053fb3b0485a6b1f487e6a658123f06013bee55610b02120/pyroaring-1.0.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d73979e1a3a6de2b7039dd9a545afa23f3b33f8b6f90a825390a34253d097f96", size = 363373, upload-time = "2026-03-19T13:56:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ff/188c16cdd75d841e50d2779ff7b5d1c5c915a6f23006ef3ab3680f48faca/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dab3d8577b143c64c1c1659b4d1c69d7fec5baa0d0c0181cdddf6b84f43af00a", size = 1914865, upload-time = "2026-03-19T13:56:09.22Z" }, + { url = "https://files.pythonhosted.org/packages/c8/12/ae7b4fa3682190597cbfb252be570358c5bc55f46f6b05db3fde66dfacc1/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dcc7d0133f46163b5390dd151e3305bd47289f7710c6e5444f38453d55b15d1c", size = 1742423, upload-time = "2026-03-19T13:56:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/f7/25/966ea0a9d857ac3f2af1eaebdbfd56e627507b890f8ff7752c32e8e57b57/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a8b8448c2f7af3b40f17dde23b6739f3b19cf8b24db6f817c549c870d50aae3", size = 2130698, upload-time = "2026-03-19T13:56:12.039Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/0d0320925cf8bc1fcb53db182359bd673bf6b434f31c6cc69e4f5312c55f/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:13ccc488cfe6a227945586090397f299fd40c1a0dc1ed25e8e58a4d068c1ed46", size = 2822746, upload-time = "2026-03-19T13:56:13.274Z" }, + { url = "https://files.pythonhosted.org/packages/b9/98/a5f6d619098e307baf71b14a4df955914e8092f459d19daf80fbbd651fa1/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dd89ebb7496325fb1b3dbe290dad35bc4da8722b5e3afd2a71b1d6e8ad981725", size = 2657370, upload-time = "2026-03-19T13:56:14.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/13/4d8beb31e4f648326b9b39c8f056fc1cb41422ef4e2be17cb15432c7fd40/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06b15bc8e0c272c3bee0c8adc29130178cd792ad99af64d7d7a1eb3069a1e0b8", size = 3088618, upload-time = "2026-03-19T13:56:16.625Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/95e223db5e60a6def8abcc2a8ebc4c71df23148a602310973c9a6964e3c5/pyroaring-1.0.4-cp312-cp312-win32.whl", hash = "sha256:4dca094f1d0e18901fa3f6b8866fa14a0f9640b22f41f5fc278c20e15e70efee", size = 202455, upload-time = "2026-03-19T13:56:18.124Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2c/5e41a91822c3bbc735382939d354e2c10bae453b18fe5a133f7fdbb33ce9/pyroaring-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:63ab0dcb24933fc9d4ca8c9fa0440f7e177183975990f756a42cddad22fd66db", size = 257479, upload-time = "2026-03-19T13:56:19.25Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d7/7bc58e807e7d6739f4f5c45964a35927e1ff7f591e3943372097d29a00a7/pyroaring-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:226079645dd4098d3619ae5fc19bf9abfd2187a74aba94a8768443e637d406fa", size = 215516, upload-time = "2026-03-19T13:56:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/45/d2/bd78613ecab5dcc17631de0de019b16470f1974e88cfb577caf131083661/pyroaring-1.0.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4171902a88df936564f39cce05e36ff5d363e122575d5cf575163917be9ec91c", size = 321192, upload-time = "2026-03-19T13:56:21.605Z" }, + { url = "https://files.pythonhosted.org/packages/be/25/9c1c57743c2bb400d09e29f174b5369054e2700343b04dce36bdb55702e3/pyroaring-1.0.4-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:cbbe3fd3baef0bf04e149031aff44485757173370c5ce326ccb5269e131377ed", size = 684346, upload-time = "2026-03-19T13:56:22.996Z" }, + { url = "https://files.pythonhosted.org/packages/36/10/3c8cdc657b7ca4f30f51e385980b205bf1b1f04cad12c856a34db380bc5a/pyroaring-1.0.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:f38ca8a8b43f6a12d606bf68d9c0348620bca29b97d315d545caa3f0a1179902", size = 362544, upload-time = "2026-03-19T13:56:24.068Z" }, + { url = "https://files.pythonhosted.org/packages/86/46/3c6177117baa1896f38818767db1cd43130b32afc8b63a7be2a0c18faab6/pyroaring-1.0.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ef35040b1bc4bb686b4206f4a4de748f6209fa578cafdf177ca3bb67f7bd552", size = 1914120, upload-time = "2026-03-19T13:56:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/f3/84/61176b1fdda1cde8572fc66fd88c1169f72fcace6b443bd8113df075b940/pyroaring-1.0.4-cp313-cp313-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15c7f8b4bfc66c005723218500af414466a52e5cd419dbdd3a899307303b4322", size = 1741664, upload-time = "2026-03-19T13:56:27.369Z" }, + { url = "https://files.pythonhosted.org/packages/48/8c/fc091d54a09f549c6fb860302676f2b3ca79ffbda23f298e59bdba693989/pyroaring-1.0.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbed00fb4c4f2ba0446e159f025beebb12c2f12dd34827551eb239438044fdb9", size = 2107765, upload-time = "2026-03-19T13:56:28.605Z" }, + { url = "https://files.pythonhosted.org/packages/95/d4/864caa6719c3c229767a1cc6c32f707c0f7a67dbe19c3be595c7f43dec4d/pyroaring-1.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79af77dfb872da8a908514628b3f6bbcd2151bf31aeafebe2527e8d6fb786c97", size = 2819767, upload-time = "2026-03-19T13:56:30.216Z" }, + { url = "https://files.pythonhosted.org/packages/3d/67/7b0c91ea00d0c3e447c7507d8e9f26d94cad1f3c24c86088840d9fc490d2/pyroaring-1.0.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:15b0b92b4dcdb636ecd0a2104ef3be6b5288f85642dd16d7d631152948c3856e", size = 2651293, upload-time = "2026-03-19T13:56:31.856Z" }, + { url = "https://files.pythonhosted.org/packages/a3/aa/1c3746750c8c9b972d421fc6eae27fc21109a5cc26b89b7d5f8d104a613d/pyroaring-1.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:68f18b8fffee671996a4ba450f015a89281f7b7516ccc70b1398f20f46858536", size = 3070775, upload-time = "2026-03-19T13:56:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/05/da/a527a6141ea8839bee27869b92584f05120e1fc47cd572e313ddf5fd9f56/pyroaring-1.0.4-cp313-cp313-win32.whl", hash = "sha256:9be9df4a9ec671fdef22581e8ac9110ca8c47c5724e90ce825d54813de8ac3d3", size = 202501, upload-time = "2026-03-19T13:56:35.473Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b1/4ab2b78ab46dd4312e68013f997b4e771ed91c6acb65a00bbeafe7ea3fe4/pyroaring-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:048b915bacfc8e4d05de4fe6c2d7f7dd9d4c7a200b3b23dc1589226ca2526b41", size = 257129, upload-time = "2026-03-19T13:56:36.746Z" }, + { url = "https://files.pythonhosted.org/packages/70/73/ba128b5bafe10f7098217f257a11b1f5ecb50947e3800101a13124562d0f/pyroaring-1.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:645af7435e9ad31a7aa6891b55797fe904c9c579fc5993b7fd59605f593188b9", size = 215209, upload-time = "2026-03-19T13:56:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/64ae48d0d7d7e8352ab894a2d38b111db87499af2d2fa98464b395a1ab86/pyroaring-1.0.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d0b14587e4ea67b7c7e16293bd934929ac1a7cced34aed11caab15466b24b8d4", size = 322966, upload-time = "2026-03-19T13:56:38.926Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/573e103a721feb22b9299c65d71bfc24872ddc487a84b61407a45750cd4d/pyroaring-1.0.4-cp314-cp314-macosx_14_0_universal2.whl", hash = "sha256:55ae44a958017ef5d355c80a1b20ecd5cf8cd796ea26c9e34ef606a2f65f49de", size = 686037, upload-time = "2026-03-19T13:56:40.148Z" }, + { url = "https://files.pythonhosted.org/packages/c8/00/0c16fcd126e1cac0280ab1f44bcac8603ecca124c36cf5c523698abe628d/pyroaring-1.0.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:316f89f7961972d07b7ed71f32335fe1b47d143b3dca1c35d58c48299f687750", size = 363100, upload-time = "2026-03-19T13:56:42.184Z" }, + { url = "https://files.pythonhosted.org/packages/51/5d/1bc58754e2c747b06ff380467a2d38b63542856bbf681b4472fa3f9e2346/pyroaring-1.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:edd767e6263fa17feae959519ee3d9bdbd0c5f03b55f89ecdff9d7136886e8f6", size = 1913082, upload-time = "2026-03-19T13:56:43.334Z" }, + { url = "https://files.pythonhosted.org/packages/23/21/8f75c12cab6e0af84464e8d8aff14bc46daa72d8d28c1fa966ba5f296705/pyroaring-1.0.4-cp314-cp314-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fc84c41de2b1370d57fea076f12246e6ab9333499ba6e8c74a5ea32168d357a", size = 1734580, upload-time = "2026-03-19T13:56:44.572Z" }, + { url = "https://files.pythonhosted.org/packages/32/30/aa3b93bbb13c9d122e6d95cebedcd3479366cee9d78094f5207b9da0642e/pyroaring-1.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abcb5083a80453ff59757882510c41e895b20c3326a37e3f683993953ae6ed1c", size = 2097410, upload-time = "2026-03-19T13:56:46.158Z" }, + { url = "https://files.pythonhosted.org/packages/74/ff/75e3a99ebc63ba2be3a484bd7b0e191209f4b5cbbcb094a057779cef4ac5/pyroaring-1.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:078ee25ed3fda098032d7f30a603f836b538e5e7e5717d8dee29b42de4547250", size = 2823136, upload-time = "2026-03-19T13:56:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/78/1d/85d398b50f4e4c47ee88e65d172d07a4bc2f2d981ca4b366e5c83b67f38c/pyroaring-1.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ed967ec4ef3b9de7ef3f7c0cf3a850b599111479654d0b7962f12acea77b24ce", size = 2635231, upload-time = "2026-03-19T13:56:48.927Z" }, + { url = "https://files.pythonhosted.org/packages/fc/33/db0761c3120a65e8b2e0ee3d75b645674c532c65c22396a219f707bf963d/pyroaring-1.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2f617e66633428659193736bc568a0462f2966ab14229ec936ebb490647caa67", size = 3059892, upload-time = "2026-03-19T13:56:50.415Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2b/4d73d056a73f65af4a58e8c691adcbcf563dd220d21f4285ebcc7d10509c/pyroaring-1.0.4-cp314-cp314-win32.whl", hash = "sha256:f0239d70d5d83227fa43a43b332bf707c6cafa7e28c14889d1602fc872934f02", size = 207536, upload-time = "2026-03-19T13:56:52.335Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/bc8b4704742debc21b4f4eebb5de8fc7d9e3ce8643b7d65c5c519eff5e42/pyroaring-1.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:06f080c8dc42af32f66f11afdc603d5bf965d9239ad8d7773f0bae0d28a4e22f", size = 264935, upload-time = "2026-03-19T13:56:53.364Z" }, + { url = "https://files.pythonhosted.org/packages/52/2e/f9146436ecf3cf5af8c581f69ebbf03dd3d1561c7b8fe39679b50a3c37b9/pyroaring-1.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:9ef52a2a1b3e6ccb3daac2dd5e8178349846d05f8cc1d0c05c551f73927cf0b4", size = 223530, upload-time = "2026-03-19T13:56:54.463Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a9/f0a925c0d857d23830fa1a12452d0976f8ba245f2e4a9f019243b6d6f0c4/pyroaring-1.0.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:91e615d6a5e114ce9297650fabd48bf1f01655cfd5bd9cb41b9b1dc0ff840e06", size = 335434, upload-time = "2026-03-19T13:56:55.548Z" }, + { url = "https://files.pythonhosted.org/packages/66/36/0c06caab704ed4573e5e4f24e5f4a99c6c9c03f24a53d0f73970517dceff/pyroaring-1.0.4-cp314-cp314t-macosx_14_0_universal2.whl", hash = "sha256:d943aebe18f017e3a37acdf8eced5b37bd9b5d5fcde8d581936ec6732b72f3c7", size = 707199, upload-time = "2026-03-19T13:56:56.622Z" }, + { url = "https://files.pythonhosted.org/packages/07/4a/0fdd0bead1a3889cc6bd0dccbc168df5a4c9fc635e40a95f13426246c7c3/pyroaring-1.0.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:01f990499a4f34a666702da16080b6a94b990c5dc5cd7f2b1f6f92a7184b94ed", size = 372204, upload-time = "2026-03-19T13:56:57.762Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8a/78cfd7c35c1e6a82bbca8e3f57f123273168b892ab92daf5138a3717e57d/pyroaring-1.0.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:236c5fb377d942c0911c7f7b1d929e192ad0165f07dca0957fa4b8ec607b4a8d", size = 1962093, upload-time = "2026-03-19T13:56:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/12f5dc15217f9d018b0b76dafb89db49833c14d58e49a0a16779b7b231ea/pyroaring-1.0.4-cp314-cp314t-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:84925878cff40ce1f5a99de0fc5b49b7cf6a6276ae5046ae80bc2ae9b5b83d83", size = 1751588, upload-time = "2026-03-19T13:57:01.097Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/7291e0a04af887f58ecd58a304dae2f979a8393ad7cd18059a744a16053b/pyroaring-1.0.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4ce889a06603931a03195b90dc11f03cfb6849c4d8504168e7325da06ce95ee", size = 2130851, upload-time = "2026-03-19T13:57:02.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/23/56d4dba6fa2aae5c862f1c46d9f8c8d53f7c3ade95e327db7e430913ef8a/pyroaring-1.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f8285cb12c7181cd29c9807f624d729c0db7bbf6f96fe7c25aba719225f85051", size = 2853020, upload-time = "2026-03-19T13:57:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/ba/25/9a3e61e85ce1e670fe3fbaac2a56eb0c8c8aa6ac3a945f5f734bf5851b7f/pyroaring-1.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:731e1f64292ee0e99cf43ef9d052c75c06f4c6dda752a6a21d062726bb077a2b", size = 2645177, upload-time = "2026-03-19T13:57:05.263Z" }, + { url = "https://files.pythonhosted.org/packages/25/43/f57f8696c050dd8b642522e9ab775987c8fec7a61ab2a0899776c7f82036/pyroaring-1.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4035852113b720f72872741be8f9760c0d2dd1640f0bc598ece7ff7ca226a3a0", size = 3087856, upload-time = "2026-03-19T13:57:06.531Z" }, + { url = "https://files.pythonhosted.org/packages/74/3a/0a9d837478912ce51d1583bc4352818e8933252207d05d3021e91103e084/pyroaring-1.0.4-cp314-cp314t-win32.whl", hash = "sha256:f9a111e668f026849a22a7e4cab628e41e48bb87bd2a9aea72bca75edcd7a6f2", size = 234160, upload-time = "2026-03-19T13:57:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/50/9d/bcd304935f20ce5f9ecb2837df69516be657e144da350a73a1899985aa38/pyroaring-1.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:92fdad43421ad2e076639f7a0d9ee815713b98bfe07dd9107ef3ea91ff0f3b37", size = 298468, upload-time = "2026-03-19T13:57:08.94Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4d/bd52a55dcef5cde4d0dda8c80000463c6929c7faba2466d9fa58fd0d48f8/pyroaring-1.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:c291428f148450e0609d5b1201b81e8248b3262770e51fc4c89768529ee36df0", size = 234175, upload-time = "2026-03-19T13:57:10.071Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dd/fe18c9eff364ab5d85ca5b5db408faddb13ca6c99211718c7c7c18fca689/pyroaring-1.0.4-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:457dbadc044ad5ceb65dbdbc9aabc2ef557d4177353a7705fbe786a8336aba81", size = 320894, upload-time = "2026-03-19T13:57:11.454Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/2686989628cc6fdd37a2ccdd8a89f38b4c7f2d15e9d57a271b524479dac3/pyroaring-1.0.4-cp39-cp39-macosx_14_0_universal2.whl", hash = "sha256:c461121b1ee5462ca0da0e5f67d836f04d29b140e4b26969647347e2e89bc058", size = 682540, upload-time = "2026-03-19T13:57:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/42e515d778abcbfde8e3efd4e3a700d01d9577d88a063f7df66b1ce32fba/pyroaring-1.0.4-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:474b25774e0ea8e36ad3f811dbec6183ba4bcf93ee95674a14a4ae96932f0ffd", size = 400256, upload-time = "2026-03-19T13:57:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e7/6f3570a34bafa1edbfef87b4ce4eb923fb3249141f9e18eed46ef50ad7fb/pyroaring-1.0.4-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f8e82d665385033eef1794d96a34cfb34da76fa6896d49756510f2f32a0c218", size = 1881153, upload-time = "2026-03-19T13:57:15.223Z" }, + { url = "https://files.pythonhosted.org/packages/af/a5/9f09bdb25c2bd58936419598e4ded328947c929a2fbaa5a8acd4ce47ff53/pyroaring-1.0.4-cp39-cp39-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cac139cbeb1941760a9568d742d29bd31eaa2ea7bb6cf54757d14be4498145b9", size = 1746808, upload-time = "2026-03-19T13:57:16.427Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/9de29d2ec8080e8bff0ba44f2429e30025f4c838d526b9b315756e17c3cf/pyroaring-1.0.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c67b59f5f794e3b8262b89148a2662199a11aac4169cd5515c6dc74efa7a3447", size = 2073909, upload-time = "2026-03-19T13:57:18.05Z" }, + { url = "https://files.pythonhosted.org/packages/41/dd/4898378a555b218630a6d5be8e278d19b24b356075ec92e03e0c8ea090c7/pyroaring-1.0.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:598f4f4f2d61f2c5e5e64319faa03866397ab74a9bb7ef99d14ecd9f1a768166", size = 2806003, upload-time = "2026-03-19T13:57:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a1/13fe01e90e31c135a2b34d87b2f66a87bc3ade61d968276b3128d3878e16/pyroaring-1.0.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3487b1531f3532f8a024a5b8a9fac97bd1696c4df2a0ca92aa9e07009fcd0a9c", size = 2605100, upload-time = "2026-03-19T13:57:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/52/c7/e40ecc9bd5c7d3c723b63563966ade47fe0ed6aa81ed85bdb3acca960fab/pyroaring-1.0.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9a438455b90bf28ea5939ea35af8926e944f9c1480dcce3feea38a0239fe6629", size = 3045090, upload-time = "2026-03-19T13:57:22.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/05/08a17f5568f3cd3680dcbf4f4314c6071ab3f82c8999f86a36c7dd222f9b/pyroaring-1.0.4-cp39-cp39-win32.whl", hash = "sha256:310495c93d8e97b07de9024b6439215a78a5cc165e103a7cda7bf183572a4329", size = 203126, upload-time = "2026-03-19T13:57:24.026Z" }, + { url = "https://files.pythonhosted.org/packages/29/00/c653b5d337db22a5d5cd361828169c182a1dbe55cf339d0b9731c5e9a29d/pyroaring-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:7dfc36328b9dcd36ab2ba6c1d3438fa8503d9f375fa29786606dcca43fd0c94c", size = 252673, upload-time = "2026-03-19T13:57:25.115Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/17ecfd5e1788901f0dce1b4c4e9e328013c5e85b854958e53054fcb7890b/pyroaring-1.0.4-cp39-cp39-win_arm64.whl", hash = "sha256:ae8334787aabb41b947d203c4430f3589d587b2d65809bac80850dbdc65fcea7", size = 216863, upload-time = "2026-03-19T13:57:26.162Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-benchmark" +version = "5.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "py-cpuinfo" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4e/782eb6df91b6a9d9afa96c2dcfc5cac62562a68eb62a02210101f886014d/pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb", size = 1330426, upload-time = "2025-09-08T23:09:21.03Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ca/2b8693d06b1db4e0c084871e4c9d7842b561d0a6ff9d780640f5e3e9eb55/pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429", size = 906559, upload-time = "2025-09-08T23:09:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b3/b99b39e2cfdcebd512959780e4d299447fd7f46010b1d88d63324e2481ec/pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d", size = 863816, upload-time = "2025-09-08T23:09:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/61/b2/018fa8e8eefb34a625b1a45e2effcbc9885645b22cdd0a68283f758351e7/pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345", size = 666735, upload-time = "2025-09-08T23:09:26.297Z" }, + { url = "https://files.pythonhosted.org/packages/01/05/8ae778f7cd7c94030731ae2305e6a38f3a333b6825f56c0c03f2134ccf1b/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968", size = 1655425, upload-time = "2025-09-08T23:09:28.172Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ad/d69478a97a3f3142f9dbbbd9daa4fcf42541913a85567c36d4cfc19b2218/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098", size = 2033729, upload-time = "2025-09-08T23:09:30.097Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6d/e3c6ad05bc1cddd25094e66cc15ae8924e15c67e231e93ed2955c401007e/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f", size = 1891803, upload-time = "2025-09-08T23:09:31.875Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a7/97e8be0daaca157511563160b67a13d4fe76b195e3fa6873cb554ad46be3/pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78", size = 567627, upload-time = "2025-09-08T23:09:33.98Z" }, + { url = "https://files.pythonhosted.org/packages/5c/91/70bbf3a7c5b04c904261ef5ba224d8a76315f6c23454251bf5f55573a8a1/pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db", size = 632315, upload-time = "2025-09-08T23:09:36.097Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b5/a4173a83c7fd37f6bdb5a800ea338bc25603284e9ef8681377cec006ede4/pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc", size = 559833, upload-time = "2025-09-08T23:09:38.183Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/57/f4/c2e978cf6b833708bad7d6396c3a20c19750585a1775af3ff13c435e1912/pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f", size = 836257, upload-time = "2025-09-08T23:10:07.635Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5f/4e10c7f57a4c92ab0fbb2396297aa8d618e6f5b9b8f8e9756d56f3e6fc52/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8", size = 800203, upload-time = "2025-09-08T23:10:09.436Z" }, + { url = "https://files.pythonhosted.org/packages/19/72/a74a007cd636f903448c6ab66628104b1fc5f2ba018733d5eabb94a0a6fb/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381", size = 758756, upload-time = "2025-09-08T23:10:11.733Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d4/30c25b91f2b4786026372f5ef454134d7f576fcf4ac58539ad7dd5de4762/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172", size = 567742, upload-time = "2025-09-08T23:10:14.732Z" }, + { url = "https://files.pythonhosted.org/packages/92/aa/ee86edad943438cd0316964020c4b6d09854414f9f945f8e289ea6fcc019/pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9", size = 544857, upload-time = "2025-09-08T23:10:16.431Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, + { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, + { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, + { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, + { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, +] + +[[package]] +name = "scipy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" }, + { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" }, + { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" }, + { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" }, + { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" }, + { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" }, + { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" }, + { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" }, + { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.49" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, + { name = "greenlet", version = "3.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/76/f908955139842c362aa877848f42f9249642d5b69e06cee9eae5111da1bd/sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f", size = 2159321, upload-time = "2026-04-03T16:50:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/24/e2/17ba0b7bfbd8de67196889b6d951de269e8a46057d92baca162889beb16d/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b", size = 3238937, upload-time = "2026-04-03T16:54:45.731Z" }, + { url = "https://files.pythonhosted.org/packages/90/1e/410dd499c039deacff395eec01a9da057125fcd0c97e3badc252c6a2d6a7/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1", size = 3237188, upload-time = "2026-04-03T16:56:53.217Z" }, + { url = "https://files.pythonhosted.org/packages/ab/06/e797a8b98a3993ac4bc785309b9b6d005457fc70238ee6cefa7c8867a92e/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339", size = 3190061, upload-time = "2026-04-03T16:54:47.489Z" }, + { url = "https://files.pythonhosted.org/packages/44/d3/5a9f7ef580af1031184b38235da6ac58c3b571df01c9ec061c44b2b0c5a6/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d", size = 3211477, upload-time = "2026-04-03T16:56:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/69/ec/7be8c8cb35f038e963a203e4fe5a028989167cc7299927b7cf297c271e37/sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3", size = 2119965, upload-time = "2026-04-03T17:00:50.009Z" }, + { url = "https://files.pythonhosted.org/packages/b5/31/0defb93e3a10b0cf7d1271aedd87251a08c3a597ee4f353281769b547b5a/sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75", size = 2142935, upload-time = "2026-04-03T17:00:51.675Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, + { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, + { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, + { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, + { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, + { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, + { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/1d/64/6eb36149b96796ecbc1e2438959d08475e1f8765acbe007f4785a603c39c/sqlalchemy-2.0.49-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43d044780732d9e0381ac8d5316f95d7f02ef04d6e4ef6dc82379f09795d993f", size = 2162373, upload-time = "2026-04-03T16:49:49.55Z" }, + { url = "https://files.pythonhosted.org/packages/b0/96/87e57cfa06af0032a7470660d33e93ad0a2480781bb7705f4312471b993e/sqlalchemy-2.0.49-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d6be30b2a75362325176c036d7fb8d19e8846c77e87683ffaa8177b35135613", size = 3237991, upload-time = "2026-04-03T17:04:07.027Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/0099d0d554313c3587155b60288a9900660afc9989bf382176a5f4d7531b/sqlalchemy-2.0.49-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d898cc2c76c135ef65517f4ddd7a3512fb41f23087b0650efb3418b8389a3cd1", size = 3237313, upload-time = "2026-04-03T17:09:53.187Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9b/a61fcb2e8439a2282e4ac0086bb613e88cd18168cddb358fa2c5790d4705/sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:059d7151fff513c53a4638da8778be7fce81a0c4854c7348ebd0c4078ddf28fe", size = 3187435, upload-time = "2026-04-03T17:04:08.956Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/2165d3f8fa593f20039505af15474f63e85ffd7998afb6218b0fc0cd98e0/sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:334edbcff10514ad1d66e3a70b339c0a29886394892490119dbb669627b17717", size = 3209446, upload-time = "2026-04-03T17:09:55.81Z" }, + { url = "https://files.pythonhosted.org/packages/23/8d/9630ddc9a4db638a7f29954b9e667a4ece41ff65e117460473ca41f06945/sqlalchemy-2.0.49-cp39-cp39-win32.whl", hash = "sha256:74ab4ee7794d7ed1b0c37e7333640e0f0a626fc7b398c07a7aef52f484fddde3", size = 2121680, upload-time = "2026-04-03T16:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5c/480f5d8c737cfb4a494f87de6e0e58a6b6346a0f4db1fa8122c89828e32d/sqlalchemy-2.0.49-cp39-cp39-win_amd64.whl", hash = "sha256:88690f4e1f0fbf5339bedbb127e240fec1fd3070e9934c0b7bef83432f779d2f", size = 2144917, upload-time = "2026-04-03T16:55:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "strictyaml" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/08/efd28d49162ce89c2ad61a88bd80e11fb77bc9f6c145402589112d38f8af/strictyaml-1.7.3.tar.gz", hash = "sha256:22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407", size = 115206, upload-time = "2023-03-10T12:50:27.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/7c/a81ef5ef10978dd073a854e0fa93b5d8021d0594b639cc8f6453c3c78a1d/strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7", size = 123917, upload-time = "2023-03-10T12:50:17.242Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "tracking-data-container" +source = { virtual = "." } + +[package.optional-dependencies] +all = [ + { name = "pandas" }, + { name = "polars" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy" }, +] +catalog = [ + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy" }, +] +pandas = [ + { name = "pandas" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +polars = [ + { name = "polars" }, +] +pyarrow = [ + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.dev-dependencies] +all = [ + { name = "fast-forward-football", marker = "python_full_version == '3.11.*'" }, + { name = "huggingface-hub" }, + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "kloppy" }, + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "maturin" }, + { name = "mplsoccer", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mplsoccer", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy" }, + { name = "pandas" }, + { name = "pandas-stubs", version = "2.2.2.240807", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pandas-stubs", version = "2.3.3.260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "pandas-stubs", version = "3.0.0.260204", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pdoc" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-benchmark" }, + { name = "ruff" }, + { name = "sqlalchemy" }, +] +benchmarks = [ + { name = "fast-forward-football", marker = "python_full_version == '3.11.*'" }, + { name = "kloppy" }, + { name = "maturin" }, + { name = "pandas" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-benchmark" }, + { name = "sqlalchemy" }, +] +dev = [ + { name = "maturin" }, + { name = "pandas" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy" }, +] +docs = [ + { name = "maturin" }, + { name = "pandas" }, + { name = "pdoc" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy" }, +] +linting = [ + { name = "maturin" }, + { name = "mypy" }, + { name = "pandas" }, + { name = "pandas-stubs", version = "2.2.2.240807", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pandas-stubs", version = "2.3.3.260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "pandas-stubs", version = "3.0.0.260204", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ruff" }, + { name = "sqlalchemy" }, +] +nb = [ + { name = "huggingface-hub" }, + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mplsoccer", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mplsoccer", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +testing = [ + { name = "maturin" }, + { name = "pandas" }, + { name = "polars" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyiceberg", version = "0.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyiceberg", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-benchmark" }, + { name = "sqlalchemy" }, +] + +[package.metadata] +requires-dist = [ + { name = "pandas", marker = "extra == 'all'", specifier = ">=1.4.4" }, + { name = "pandas", marker = "extra == 'pandas'", specifier = ">=1.4.4" }, + { name = "polars", marker = "extra == 'all'", specifier = ">=0.16.14" }, + { name = "polars", marker = "extra == 'polars'", specifier = ">=0.16.14" }, + { name = "pyarrow", marker = "extra == 'all'", specifier = ">=8.0.0" }, + { name = "pyarrow", marker = "extra == 'pandas'", specifier = ">=8.0.0" }, + { name = "pyarrow", marker = "extra == 'pyarrow'", specifier = ">=8.0.0" }, + { name = "pyiceberg", marker = "extra == 'all'", specifier = ">=0.8.1" }, + { name = "pyiceberg", marker = "extra == 'catalog'", specifier = ">=0.8.1" }, + { name = "sqlalchemy", marker = "extra == 'all'", specifier = ">=2.0.0" }, + { name = "sqlalchemy", marker = "extra == 'catalog'", specifier = ">=2.0.0" }, +] +provides-extras = ["all", "catalog", "pandas", "polars", "pyarrow"] + +[package.metadata.requires-dev] +all = [ + { name = "fast-forward-football", marker = "python_full_version == '3.11.*'", specifier = ">=0.1.1" }, + { name = "huggingface-hub", specifier = ">=1.6.0" }, + { name = "ipykernel" }, + { name = "kloppy", specifier = ">=3.15.0" }, + { name = "matplotlib", specifier = ">=3.5.3" }, + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "mplsoccer", specifier = ">=0.1.1" }, + { name = "mypy", specifier = ">=1.11.2,<2" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "pandas-stubs" }, + { name = "pdoc" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "pytest", specifier = ">=7.1.3" }, + { name = "pytest-benchmark", specifier = ">=4.0.0,<6" }, + { name = "ruff", specifier = ">=0.12,<0.15" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] +benchmarks = [ + { name = "fast-forward-football", marker = "python_full_version == '3.11.*'", specifier = ">=0.1.1" }, + { name = "kloppy", specifier = ">=3.15.0" }, + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "pytest", specifier = ">=7.1.3" }, + { name = "pytest-benchmark", specifier = ">=4.0.0,<6" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] +dev = [ + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] +docs = [ + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "pdoc" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] +linting = [ + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "mypy", specifier = ">=1.11.2,<2" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "pandas-stubs" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "ruff", specifier = ">=0.12,<0.15" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] +nb = [ + { name = "huggingface-hub", specifier = ">=1.6.0" }, + { name = "ipykernel" }, + { name = "matplotlib", specifier = ">=3.5.3" }, + { name = "mplsoccer", specifier = ">=0.1.1" }, +] +testing = [ + { name = "maturin", specifier = ">=1.7.0,<2.0" }, + { name = "pandas", specifier = ">=1.4.4" }, + { name = "polars", specifier = ">=0.16.14" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=8.0.0" }, + { name = "pyiceberg", specifier = ">=0.8.1" }, + { name = "pytest", specifier = ">=7.1.3" }, + { name = "pytest-benchmark", specifier = ">=4.0.0,<6" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typer" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "annotated-doc", marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "shellingham", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ae/93d16574e66dfe4c2284ffdaca4b0320ade32858cb2cc586c8dd79f127c5/typer-0.23.2.tar.gz", hash = "sha256:a99706a08e54f1aef8bb6a8611503808188a4092808e86addff1828a208af0de", size = 120162, upload-time = "2026-02-16T18:52:40.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2c/dee705c427875402200fe779eb8a3c00ccb349471172c41178336e9599cc/typer-0.23.2-py3-none-any.whl", hash = "sha256:e9c8dc380f82450b3c851a9b9d5a0edf95d1d6456ae70c517d8b06a50c7a9978", size = 56834, upload-time = "2026-02-16T18:52:39.308Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "annotated-doc", marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "shellingham", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "types-pytz" +version = "2025.2.0.20251108" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/ff/c047ddc68c803b46470a357454ef76f4acd8c1088f5cc4891cdd909bfcf6/types_pytz-2025.2.0.20251108.tar.gz", hash = "sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb", size = 10961, upload-time = "2025-11-08T02:55:57.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c1/56ef16bf5dcd255155cc736d276efa6ae0a5c26fd685e28f0412a4013c01/types_pytz-2025.2.0.20251108-py3-none-any.whl", hash = "sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c", size = 10116, upload-time = "2025-11-08T02:55:56.194Z" }, +] + +[[package]] +name = "types-pytz" +version = "2026.1.1.20260304" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/56/2f12a15ea8c5615c8fb896c4fbbb527ab1c0f776ed5860c6fc9ec26ea2c7/types_pytz-2026.1.1.20260304.tar.gz", hash = "sha256:0c3542d8e9b0160b424233440c52b83d6f58cae4b85333d54e4f961cf013e117", size = 11198, upload-time = "2026-03-04T03:57:24.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/b8/e77c355f179dc89d44e7ca6dbf7a46e650806df1d356a5462e5829fccea5/types_pytz-2026.1.1.20260304-py3-none-any.whl", hash = "sha256:175332c1cf7bd6b1cc56b877f70bf02def1a3f75e5adcc05385ce2c3c70e6500", size = 10126, upload-time = "2026-03-04T03:57:23.481Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "idna", marker = "python_full_version < '3.10'" }, + { name = "multidict", marker = "python_full_version < '3.10'" }, + { name = "propcache", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, + { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, + { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, + { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, + { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, + { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, + { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "multidict", marker = "python_full_version >= '3.10'" }, + { name = "propcache", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0d/9cc638702f6fc3c7a3685bcc8cf2a9ed7d6206e932a49f5242658047ef51/yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107", size = 123764, upload-time = "2026-03-01T22:04:09.7Z" }, + { url = "https://files.pythonhosted.org/packages/7a/35/5a553687c5793df5429cd1db45909d4f3af7eee90014888c208d086a44f0/yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d", size = 86282, upload-time = "2026-03-01T22:04:11.892Z" }, + { url = "https://files.pythonhosted.org/packages/68/2e/c5a2234238f8ce37a8312b52801ee74117f576b1539eec8404a480434acc/yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05", size = 86053, upload-time = "2026-03-01T22:04:13.292Z" }, + { url = "https://files.pythonhosted.org/packages/74/3f/bbd8ff36fb038622797ffbaf7db314918bb4d76f1cc8a4f9ca7a55fe5195/yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d", size = 99395, upload-time = "2026-03-01T22:04:15.133Z" }, + { url = "https://files.pythonhosted.org/packages/77/04/9516bc4e269d2a3ec9c6779fcdeac51ce5b3a9b0156f06ac7152e5bba864/yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748", size = 92143, upload-time = "2026-03-01T22:04:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/c7/63/88802d1f6b1cb1fc67d67a58cd0cf8a1790de4ce7946e434240f1d60ab4a/yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764", size = 107643, upload-time = "2026-03-01T22:04:18.519Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4f9b838f4d8bdd6f0f385aed8bbf21c71ed11a0b9983305c302cbd557815/yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007", size = 108700, upload-time = "2026-03-01T22:04:20.373Z" }, + { url = "https://files.pythonhosted.org/packages/50/12/95a1d33f04a79c402664070d43b8b9f72dc18914e135b345b611b0b1f8cc/yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4", size = 102769, upload-time = "2026-03-01T22:04:23.055Z" }, + { url = "https://files.pythonhosted.org/packages/86/65/91a0285f51321369fd1a8308aa19207520c5f0587772cfc2e03fc2467e90/yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26", size = 101114, upload-time = "2026-03-01T22:04:25.031Z" }, + { url = "https://files.pythonhosted.org/packages/58/80/c7c8244fc3e5bc483dc71a09560f43b619fab29301a0f0a8f936e42865c7/yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769", size = 98883, upload-time = "2026-03-01T22:04:27.281Z" }, + { url = "https://files.pythonhosted.org/packages/86/e7/71ca9cc9ca79c0b7d491216177d1aed559d632947b8ffb0ee60f7d8b23e3/yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716", size = 94172, upload-time = "2026-03-01T22:04:28.554Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3f/6c6c8a0fe29c26fb2db2e8d32195bb84ec1bfb8f1d32e7f73b787fcf349b/yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993", size = 107010, upload-time = "2026-03-01T22:04:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/56/38/12730c05e5ad40a76374d440ed8b0899729a96c250516d91c620a6e38fc2/yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0", size = 100285, upload-time = "2026-03-01T22:04:31.752Z" }, + { url = "https://files.pythonhosted.org/packages/34/92/6a7be9239f2347234e027284e7a5f74b1140cc86575e7b469d13fba1ebfe/yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750", size = 108230, upload-time = "2026-03-01T22:04:33.844Z" }, + { url = "https://files.pythonhosted.org/packages/5e/81/4aebccfa9376bd98b9d8bfad20621a57d3e8cfc5b8631c1fa5f62cdd03f4/yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6", size = 103008, upload-time = "2026-03-01T22:04:35.856Z" }, + { url = "https://files.pythonhosted.org/packages/38/0f/0b4e3edcec794a86b853b0c6396c0a888d72dfce19b2d88c02ac289fb6c1/yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d", size = 83073, upload-time = "2026-03-01T22:04:38.268Z" }, + { url = "https://files.pythonhosted.org/packages/a0/71/ad95c33da18897e4c636528bbc24a1dd23fe16797de8bc4ec667b8db0ba4/yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb", size = 87328, upload-time = "2026-03-01T22:04:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/dfa369523c79bccf9c9c746b0a63eb31f65db9418ac01275f7950962e504/yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220", size = 82463, upload-time = "2026-03-01T22:04:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/a2/aa/60da938b8f0997ba3a911263c40d82b6f645a67902a490b46f3355e10fae/yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99", size = 123641, upload-time = "2026-03-01T22:04:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c", size = 86248, upload-time = "2026-03-01T22:04:44.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432", size = 85988, upload-time = "2026-03-01T22:04:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6c/4a90d59c572e46b270ca132aca66954f1175abd691f74c1ef4c6711828e2/yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a", size = 100566, upload-time = "2026-03-01T22:04:47.639Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/c438fb5108047e629f6282a371e6e91cf3f97ee087c4fb748a1f32ceef55/yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05", size = 92079, upload-time = "2026-03-01T22:04:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/d9/13/d269aa1aed3e4f50a5a103f96327210cc5fa5dd2d50882778f13c7a14606/yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83", size = 108741, upload-time = "2026-03-01T22:04:50.838Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/115b16f22c37ea4437d323e472945bea97301c8ec6089868fa560abab590/yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c", size = 108099, upload-time = "2026-03-01T22:04:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598", size = 102678, upload-time = "2026-03-01T22:04:55.176Z" }, + { url = "https://files.pythonhosted.org/packages/85/59/cd98e556fbb2bf8fab29c1a722f67ad45c5f3447cac798ab85620d1e70af/yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b", size = 100803, upload-time = "2026-03-01T22:04:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/b39770b56d4a9f0bb5f77e2f1763cd2d75cc2f6c0131e3b4c360348fcd65/yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c", size = 100163, upload-time = "2026-03-01T22:04:58.492Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/6980f99ab00e1f0ff67cb84766c93d595b067eed07439cfccfc8fb28c1a6/yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788", size = 93859, upload-time = "2026-03-01T22:05:00.268Z" }, + { url = "https://files.pythonhosted.org/packages/38/69/912e6c5e146793e5d4b5fe39ff5b00f4d22463dfd5a162bec565ac757673/yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222", size = 108202, upload-time = "2026-03-01T22:05:02.273Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/35ca6767524687ad64e5f5c31ad54bc76d585585a9fcb40f649e7e82ffed/yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb", size = 99866, upload-time = "2026-03-01T22:05:03.597Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1c/1a3387ee6d73589f6f2a220ae06f2984f6c20b40c734989b0a44f5987308/yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc", size = 107852, upload-time = "2026-03-01T22:05:04.986Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/35c0750fcd5a3f781058bfd954515dd4b1eab45e218cbb85cf11132215f1/yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2", size = 102919, upload-time = "2026-03-01T22:05:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/9a1979aec4a81896d597bcb2177827f2dbee3f5b7cc48b2d0dadb644b41d/yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5", size = 82602, upload-time = "2026-03-01T22:05:08.444Z" }, + { url = "https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46", size = 87461, upload-time = "2026-03-01T22:05:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/93/95/07e3553fe6f113e6864a20bdc53a78113cda3b9ced8784ee52a52c9f80d8/yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928", size = 82336, upload-time = "2026-03-01T22:05:11.554Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/28efd1d371f1acd037ac64ed1c5e2b41514a6cc937dd6ab6a13ab9f0702f/zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd", size = 795256, upload-time = "2025-09-14T22:15:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/ef34ef77f1ee38fc8e4f9775217a613b452916e633c4f1d98f31db52c4a5/zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7", size = 640565, upload-time = "2025-09-14T22:15:58.177Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1b/4fdb2c12eb58f31f28c4d28e8dc36611dd7205df8452e63f52fb6261d13e/zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550", size = 5345306, upload-time = "2025-09-14T22:16:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/73/28/a44bdece01bca027b079f0e00be3b6bd89a4df180071da59a3dd7381665b/zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d", size = 5055561, upload-time = "2025-09-14T22:16:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/e9/74/68341185a4f32b274e0fc3410d5ad0750497e1acc20bd0f5b5f64ce17785/zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b", size = 5402214, upload-time = "2025-09-14T22:16:04.109Z" }, + { url = "https://files.pythonhosted.org/packages/8b/67/f92e64e748fd6aaffe01e2b75a083c0c4fd27abe1c8747fee4555fcee7dd/zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0", size = 5449703, upload-time = "2025-09-14T22:16:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e5/6d36f92a197c3c17729a2125e29c169f460538a7d939a27eaaa6dcfcba8e/zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0", size = 5556583, upload-time = "2025-09-14T22:16:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/41939e60d8d7ebfe2b747be022d0806953799140a702b90ffe214d557638/zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd", size = 5045332, upload-time = "2025-09-14T22:16:10.444Z" }, + { url = "https://files.pythonhosted.org/packages/b3/87/d3ee185e3d1aa0133399893697ae91f221fda79deb61adbe998a7235c43f/zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701", size = 5572283, upload-time = "2025-09-14T22:16:12.128Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/58635ae6104df96671076ac7d4ae7816838ce7debd94aecf83e30b7121b0/zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1", size = 4959754, upload-time = "2025-09-14T22:16:14.225Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/57e9cb0a9983e9a229dd8fd2e6e96593ef2aa82a3907188436f22b111ccd/zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150", size = 5266477, upload-time = "2025-09-14T22:16:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/ee891e5edf33a6ebce0a028726f0bbd8567effe20fe3d5808c42323e8542/zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab", size = 5440914, upload-time = "2025-09-14T22:16:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/a8522c28c08031a9521f27abc6f78dbdee7312a7463dd2cfc658b813323b/zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e", size = 5819847, upload-time = "2025-09-14T22:16:20.559Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/4c91411805c3f7b6f31c60e78ce347ca48f6f16d552fc659af6ec3b73202/zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74", size = 5363131, upload-time = "2025-09-14T22:16:22.206Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d6/8c4bd38a3b24c4c7676a7a3d8de85d6ee7a983602a734b9f9cdefb04a5d6/zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa", size = 436469, upload-time = "2025-09-14T22:16:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/93/90/96d50ad417a8ace5f841b3228e93d1bb13e6ad356737f42e2dde30d8bd68/zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e", size = 506100, upload-time = "2025-09-14T22:16:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/d0a405dad6ab6f9f759c26d866cca66cb209bff6f8db656074d662a953dd/zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0", size = 795263, upload-time = "2025-09-14T22:18:21.683Z" }, + { url = "https://files.pythonhosted.org/packages/ca/aa/ceb8d79cbad6dabd4cb1178ca853f6a4374d791c5e0241a0988173e2a341/zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2", size = 640560, upload-time = "2025-09-14T22:18:22.867Z" }, + { url = "https://files.pythonhosted.org/packages/88/cd/2cf6d476131b509cc122d25d3416a2d0aa17687ddbada7599149f9da620e/zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df", size = 5344244, upload-time = "2025-09-14T22:18:24.724Z" }, + { url = "https://files.pythonhosted.org/packages/5c/71/e14820b61a1c137966b7667b400b72fa4a45c836257e443f3d77607db268/zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53", size = 5054550, upload-time = "2025-09-14T22:18:26.445Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ce/26dc5a6fa956be41d0e984909224ed196ee6f91d607f0b3fd84577741a77/zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3", size = 5401150, upload-time = "2025-09-14T22:18:28.745Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/402cab5edcfe867465daf869d5ac2a94930931c0989633bc01d6a7d8bd68/zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362", size = 5448595, upload-time = "2025-09-14T22:18:30.475Z" }, + { url = "https://files.pythonhosted.org/packages/86/b2/fc50c58271a1ead0e5a0a0e6311f4b221f35954dce438ce62751b3af9b68/zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530", size = 5555290, upload-time = "2025-09-14T22:18:32.336Z" }, + { url = "https://files.pythonhosted.org/packages/d2/20/5f72d6ba970690df90fdd37195c5caa992e70cb6f203f74cc2bcc0b8cf30/zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb", size = 5043898, upload-time = "2025-09-14T22:18:34.215Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f1/131a0382b8b8d11e84690574645f528f5c5b9343e06cefd77f5fd730cd2b/zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751", size = 5571173, upload-time = "2025-09-14T22:18:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/53/f6/2a37931023f737fd849c5c28def57442bbafadb626da60cf9ed58461fe24/zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577", size = 4958261, upload-time = "2025-09-14T22:18:38.098Z" }, + { url = "https://files.pythonhosted.org/packages/b5/52/ca76ed6dbfd8845a5563d3af4e972da3b9da8a9308ca6b56b0b929d93e23/zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7", size = 5265680, upload-time = "2025-09-14T22:18:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/7a/59/edd117dedb97a768578b49fb2f1156defb839d1aa5b06200a62be943667f/zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936", size = 5439747, upload-time = "2025-09-14T22:18:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/75/71/c2e9234643dcfbd6c5e975e9a2b0050e1b2afffda6c3a959e1b87997bc80/zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388", size = 5818805, upload-time = "2025-09-14T22:18:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/f5/93/8ebc19f0a31c44ea0e7348f9b0d4b326ed413b6575a3c6ff4ed50222abb6/zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27", size = 5362280, upload-time = "2025-09-14T22:18:45.625Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/29cc59d4a9d51b3fd8b477d858d0bd7ab627f700908bf1517f46ddd470ae/zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649", size = 436460, upload-time = "2025-09-14T22:18:49.077Z" }, + { url = "https://files.pythonhosted.org/packages/41/b5/bc7a92c116e2ef32dc8061c209d71e97ff6df37487d7d39adb51a343ee89/zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860", size = 506097, upload-time = "2025-09-14T22:18:47.342Z" }, +]