From df0fcbefad8258f897532b952596592340b89b0c Mon Sep 17 00:00:00 2001 From: Max de Danschutter Date: Tue, 6 Oct 2026 15:57:25 +0200 Subject: [PATCH] Add Evolve Charts: an easy tool for comparing Evolve results A local Streamlit app, styled like Evolve, for journalists who don't know Python or git. It reads every Evolve export (the score screen's Export zip, evolve_results.csv, scores.csv, the per-frame CSV and deep_analysis.json) and has a page per question: Results, Scores, GPU time per workload category and per pass, GPU sensors and CSV export. Charts download as PNG or SVG with a source line. Which pass belongs to which workload category is generated from Evolve's frame bucketing source by tools/sync_categories.py. Downloadable without git: tools/package.py builds Evolve-Charts.zip with double-click launchers for Windows and macOS, and a release workflow publishes it on v* tags. Dependencies are locked in evolve-charts.py.lock. Replaces the old compare_deep_analysis.py script. Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 3 + .github/workflows/ci.yml | 25 + .github/workflows/release.yml | 32 + .gitignore | 8 + .streamlit/config.toml | 52 ++ README.md | 145 +++- Start Evolve Charts.bat | 36 + Start Evolve Charts.command | 8 + evolve-charts.py | 36 + evolve-charts.py.lock | 1346 ++++++++++++++++++++++++++++++ evolve_charts/__init__.py | 0 evolve_charts/categories.py | 41 + evolve_charts/charts.py | 237 ++++++ evolve_charts/loader.py | 334 ++++++++ evolve_charts/scope_catalog.py | 454 ++++++++++ evolve_charts/stats.py | 211 +++++ evolve_charts/ui.py | 762 +++++++++++++++++ exports/README.txt | 2 + requirements.txt | 4 - ruff.toml | 1 + scripts/compare_deep_analysis.py | 173 ---- start-evolve-charts.sh | 16 + static/Spartan-Bold.ttf | Bin 0 -> 63120 bytes static/Spartan-Regular.ttf | Bin 0 -> 64308 bytes static/evolve-icon.png | Bin 0 -> 7067 bytes static/evolve-logo.png | Bin 0 -> 23403 bytes tests/conftest.py | 95 +++ tests/test_loader.py | 137 +++ tests/test_stats.py | 97 +++ tests/test_ui.py | 70 ++ tools/package.py | 78 ++ tools/sync_categories.py | 85 ++ 32 files changed, 4273 insertions(+), 215 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .streamlit/config.toml create mode 100644 Start Evolve Charts.bat create mode 100755 Start Evolve Charts.command create mode 100644 evolve-charts.py create mode 100644 evolve-charts.py.lock create mode 100644 evolve_charts/__init__.py create mode 100644 evolve_charts/categories.py create mode 100644 evolve_charts/charts.py create mode 100644 evolve_charts/loader.py create mode 100644 evolve_charts/scope_catalog.py create mode 100644 evolve_charts/stats.py create mode 100644 evolve_charts/ui.py create mode 100644 exports/README.txt delete mode 100644 requirements.txt create mode 100644 ruff.toml delete mode 100644 scripts/compare_deep_analysis.py create mode 100755 start-evolve-charts.sh create mode 100644 static/Spartan-Bold.ttf create mode 100644 static/Spartan-Regular.ttf create mode 100644 static/evolve-icon.png create mode 100644 static/evolve-logo.png create mode 100644 tests/conftest.py create mode 100644 tests/test_loader.py create mode 100644 tests/test_stats.py create mode 100644 tests/test_ui.py create mode 100644 tools/package.py create mode 100644 tools/sync_categories.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7177b76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.bat text eol=crlf +*.sh text eol=lf +*.command text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..06dcca1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v6 + - name: Lint + run: | + uvx ruff check evolve_charts tests tools evolve-charts.py + uvx ruff format --check evolve_charts tests tools evolve-charts.py + # Test against exactly the libraries the download installs (evolve-charts.py.lock). + - name: Install + run: | + uv venv --python 3.12 + uv export --script evolve-charts.py --no-hashes > requirements.txt + uv pip install -r requirements.txt pytest + - name: Test + run: uv run --no-project pytest -q diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..751a7bf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release Evolve Charts + +# Push a tag like v1.0.0 to publish a release. Its download is always at +# https://github.com/Traverse-Research/evolve-scripts/releases/latest/download/Evolve-Charts.zip +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v6 + - name: Test + run: | + uv venv --python 3.12 + uv export --script evolve-charts.py --no-hashes > requirements.txt + uv pip install -r requirements.txt pytest + uv run --no-project pytest -q + - name: Package + run: python3 tools/package.py "${GITHUB_REF_NAME#v}" + - name: Publish + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" dist/Evolve-Charts.zip \ + --title "Evolve Charts ${GITHUB_REF_NAME#v}" \ + --notes "Download **Evolve-Charts.zip**, extract it, and double-click *Start Evolve Charts*. See *HOW TO START.txt* inside the zip." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8d6f5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +.venv/ +.pytest_cache/ +exports/* +!exports/README.txt +dist/ +VERSION +requirements.txt diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..95b00c3 --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,52 @@ +# Evolve Charts settings. `evolve-charts.py` starts Streamlit from this folder so it picks them up. + +[server] +# Keep everything on this computer: only reachable from this machine. +address = "localhost" +# Evolve exports with deep analysis are several hundred megabytes. +maxUploadSize = 2048 +# Serves `static/`: the Spartan font and the Evolve logo. +enableStaticServing = true + +[browser] +gatherUsageStats = false + +[client] +toolbarMode = "minimal" + +# Evolve's look, from `evolve/crates/evolve/src/ui/styling.rs`. +[theme] +base = "dark" +# RED_MEDIUM: Evolve's primary (orange) buttons +primaryColor = "#FD663A" +# GREEN_DARKER: the background panel +backgroundColor = "#0D3618" +# GREEN_DARK: panels and widgets +secondaryBackgroundColor = "#0F3F1D" +# Evolve's text colour on green +textColor = "#EFEEF0" +# GREEN_MEDIUM +borderColor = "#3E703C" +dataframeBorderColor = "#3E703C" +dataframeHeaderBackgroundColor = "#0F3F1D" +linkColor = "#FD663A" +greenColor = "#94C191" +redColor = "#CC3232" +orangeColor = "#FD663A" +yellowColor = "#F8DD49" +blueColor = "#619CD9" +# BUTTON_ROUNDING +baseRadius = "4px" +font = "Spartan, sans-serif" +headingFont = "Spartan, sans-serif" +headingFontWeights = 700 + +[[theme.fontFaces]] +family = "Spartan" +url = "app/static/Spartan-Regular.ttf" +weight = 400 + +[[theme.fontFaces]] +family = "Spartan" +url = "app/static/Spartan-Bold.ttf" +weight = 700 diff --git a/README.md b/README.md index cee4809..08c2e8a 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,115 @@
-# πŸ“œ Evolve scripts +# πŸ“Š Evolve Charts -**Evolutionary benchmarking software** +**Turn Evolve deep analysis exports into publication-ready charts** [![Banner](./docs/images/banner.png)](https://traverseresearch.nl)
-## πŸ‘·β€β™€οΈ Requirements - -Please ensure the following dependencies are installed before running scripts from this repository: - -- Python 3.7 -- ADB (Android Debug Bridge) - - -## πŸ“Š Comparing deep analysis output - -Using the `compare_deep_analysis.py` script located in the `scripts` directory, you can compare the results of two separate deep analysis output files in multiple ways. For the analysis methods, the scripts will first do an attempt to average over all loop iterations of the output. If you ran Evolve with `--looping 5`, each frame in the output will use the mean from each frame from each of the Evolve benchmark iterations. - -## Additional Requirements -In addition to the requirements listed in [Requirements](#πŸ‘·β€β™€οΈ-requirements), this script also requires the use of several python packages. These can be installed as follows: - -```sh -python -m pip install -r requirements.txt -``` - -## Usage -```sh -usage: Evolve Deep Analysis Comparison [-h] [--pass_mean_comparison PASS_MEAN_COMPARISON] [--pass_stdev_comparison PASS_STDEV_COMPARISON] deep_analysis_file deep_analysis_file -``` - -### Parameters: - -- `deep_analysis_file` - The path(s) to the input files to compare, produced by Evolve's deep analysis. -- `--pass_mean_comparison [filename]` - The csv file to which to output a comparison of the mean execution times of the render passes with the highest difference in mean execution times between the two. Example: `--pass_mean_comparison mean_difference.csv`. -- `--pass_stdev_comparison [filename]` - The csv file to which to output a comparison of the standard deviation over execution times of the render passes with the highest difference in standard deviation over the execution time, between the two. Example: `--pass_stdev_comparison stdev_difference.csv`. - -At least one of `--pass_mean_comparison` or `--pass_stdev_comparison` arguments is required, as the script doesn't output any information by default. - -### Example usage -```sh -python compare_deep_analysis.py --pass_mean_comparison mean_comparison.csv --pass_stdev_comparison stdev_comparison.csv deep_analysis_gpu_1.json deep_analysis_gpu_2.json -``` \ No newline at end of file +Evolve Charts compares Evolve benchmark results: scores, GPU time per workload category and per pass, +and GPU sensors, across GPUs and settings. You don't need to know Python or git. It opens as a page in +your web browser, and **everything stays on your computer**. Nothing is uploaded. + +## ⬇️ Download + +**[Download Evolve Charts (Evolve-Charts.zip)](https://github.com/Traverse-Research/evolve-scripts/releases/latest/download/Evolve-Charts.zip)** + +1. **Extract the zip.** Right-click it and choose **Extract All…** (Windows) or double-click it (macOS), + for example to your Desktop. Don't start the tool from inside the zip. +2. **Start the tool** from the extracted *Evolve Charts* folder: + - **Windows:** double-click **Start Evolve Charts**. If Windows warns about the file, click + **More info β€Ί Run anyway**. + - **macOS:** right-click **Start Evolve Charts.command** and choose **Open** (the first time only; + after that a double-click works). + - **Linux:** run `./start-evolve-charts.sh` in a terminal. + + The first start installs [uv](https://docs.astral.sh/uv/), a small helper that downloads Python and + the libraries the tool uses (about 150 MB). This needs an internet connection, takes a few minutes and + only happens once. Keep the window that opens running while you use the tool; close it when you're + done. +3. **Export your results from Evolve.** After a benchmark run, click **Export** on the score screen. Do + this on every GPU or setting you want to compare, using the same benchmark on each. Scores and + per-frame data are always included; deep analysis (per-pass timings) needs an Evolve Professional + licence. +4. **Add your results.** Your browser opens Evolve Charts. Drag the export files onto *Add Evolve + exports*, or put them in the `exports` folder so they load automatically every time. +5. **Name your results** on their cards (for example "RTX 4070 Ti" or "Gen 1"). These names appear in + every chart. + +To update, download the zip again and replace the old folder (keep your `exports` folder if you use it). + +## πŸ—ΊοΈ What's in the tool + +The tool reads every Evolve export: the **Export** zip from the score screen, and the files Evolve's +command line writes (`evolve_results.csv`, `scores.csv`, the per-frame CSV and `deep_analysis.json`). +It has a page per question, in the bar at the top: + +| Page | What it shows | Needs | +|---|---|---| +| **Results** | Load and name exports; GPU, driver, OS, Evolve version and what each file contains | | +| **Scores** | Evolve's scores side by side, with the workload category each one scores, and the differences | scores | +| **GPU time** | Where the GPU spends its time each frame, per workload category; then one workload category over the run and the passes it's made of | per-frame data; the passes need deep analysis | +| **Passes** | Every render pass in one searchable table; tick passes to plot them over the run | deep analysis | +| **GPU sensors** | Clocks, power, temperature and more during the run, to spot throttling | per-frame data | +| **Export** | CSV files for Excel, Google Sheets, Datawrapper and Flourish | | + +On every page except Results, a bar at the top shows the loaded results and lets you pick the one to +**compare against**. + +**How Evolve measures a GPU.** Evolve times **passes**: single blocks of GPU work. A **workload +category** is GPU work that logically belongs together, and what Evolve scores. All ray tracing work, +such as the *shadow-raytrace-inline* pass, is the *Raytracing* workload: the *Ray Tracing* score. There is +one score per workload category and no total score. + +**Downloading a chart:** hover over it and click the πŸ“· camera icon in its top-right corner. Use +**Charts** at the top right to choose PNG or SVG and whether charts get a source line with the GPUs, +drivers and Evolve version. **Help** explains how the numbers are made. + +## πŸ“ How to read the numbers + +- **GPU time per frame** is how long the GPU spent on that work, averaged over every frame of the + run. The first second is skipped because it includes one-off setup work. Lines over time are + smoothed over one second. +- Passes Evolve doesn't assign to a workload category (such as drawing the user interface) are left + out; the *GPU time* page says how much that is. +- Workload categories add up to the GPU work shown. That can differ a little from the time between frames, + because a GPU can idle between passes or run passes at the same time. +- **Whiskers** on bar charts show the range covering 90% of frames (5th to 95th percentile). +- Only compare results from the **same Evolve version and benchmark settings**. The tool warns you + when the Evolve versions or graphics APIs differ. + +## ❓ Troubleshooting + +- **A result is missing from *Passes***: its export has no deep analysis, which + needs an Evolve Professional licence. Its card shows βœ— Deep analysis; scores and workload + categories still work. +- **The browser doesn't open**: open yourself while the black window is open. +- **Anything else**: send a screenshot of the black window to Traverse Research. + +## πŸ› οΈ For developers + +- Run without the launcher: `uv run evolve-charts.py`. Dependencies are declared inline + ([PEP 723](https://peps.python.org/pep-0723/)), so no virtual environment is needed. +- Code lives in `evolve_charts/`: + - `loader.py` reads export zips into tidy tables. + - `categories.py` describes Evolve's workload categories and which pass belongs to which. + - `stats.py` computes the summaries. + - `charts.py` builds the Plotly figures. + - `ui.py` is the Streamlit page. +- The look follows Evolve's `crates/evolve/src/ui/styling.rs`: theme colours and the Spartan font are set in + `.streamlit/config.toml`, and the fonts, logo and icon live in `static/`. Chart colours are in `charts.py`. +- Which pass belongs to which workload category comes from Evolve's Rust source. After pass names + change in Evolve, regenerate the list with `python tools/sync_categories.py` (it reads + `../evolve/crates/evolve-frame-bucketing/src/lib.rs` by default). +- Dependencies are locked in `evolve-charts.py.lock`; refresh it with `uv lock --script evolve-charts.py --upgrade`. +- Tests: `uv export --script evolve-charts.py --no-hashes > requirements.txt`, then + `uv venv && uv pip install -r requirements.txt pytest && uv run --no-project pytest`. CI runs the same. +- Releases: push a tag like `v1.0.0`. The release workflow tests, runs `tools/package.py` and attaches + `Evolve-Charts.zip` to a GitHub release; the download link above always points to the newest one. + +## πŸ“± Other scripts + +- `scripts/run_on_android.py` starts Evolve on an Android device with command-line arguments. See + [docs/Android.md](docs/Android.md). diff --git a/Start Evolve Charts.bat b/Start Evolve Charts.bat new file mode 100644 index 0000000..003d346 --- /dev/null +++ b/Start Evolve Charts.bat @@ -0,0 +1,36 @@ +@echo off +rem Double-click this file to start Evolve Charts. A page opens in your browser. +cd /d "%~dp0" +set "PATH=%USERPROFILE%\.local\bin;%PATH%" + +if not exist "evolve-charts.py" ( + echo Evolve Charts can't start from inside the zip file. + echo Right-click the zip, choose "Extract All...", then open the extracted folder + echo and double-click "Start Evolve Charts" there. + echo. + pause + exit /b 1 +) + +where uv >nul 2>nul +if errorlevel 1 ( + echo Evolve Charts needs "uv" to download Python and the libraries it uses. + echo It will now be installed for your user account from https://astral.sh/uv + echo. + powershell -NoProfile -ExecutionPolicy ByPass -Command "irm https://astral.sh/uv/install.ps1 | iex" + if errorlevel 1 goto failed +) + +echo Starting Evolve Charts. The first start downloads Python and the libraries it uses +echo (about 150 MB) and can take a few minutes; later starts are quick. +echo Keep this window open while you use the tool; close it to stop. +echo. +uv run evolve-charts.py +if errorlevel 1 goto failed +exit /b 0 + +:failed +echo. +echo Something went wrong. Please send a screenshot of this window to Traverse Research. +pause +exit /b 1 diff --git a/Start Evolve Charts.command b/Start Evolve Charts.command new file mode 100755 index 0000000..ca376f0 --- /dev/null +++ b/Start Evolve Charts.command @@ -0,0 +1,8 @@ +#!/bin/sh +# Double-click this file on macOS to start Evolve Charts. A page opens in your browser. +cd "$(dirname "$0")" +if ! sh ./start-evolve-charts.sh; then + echo + echo "Something went wrong. Please send a screenshot of this window to Traverse Research." + read -r _ +fi diff --git a/evolve-charts.py b/evolve-charts.py new file mode 100644 index 0000000..8c79bee --- /dev/null +++ b/evolve-charts.py @@ -0,0 +1,36 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "streamlit>=1.65", +# "plotly>=6", +# "pandas>=2.2", +# ] +# /// +"""Evolve Charts: turn Evolve deep analysis exports into charts. + +Start it by double-clicking `Start Evolve Charts.bat` (Windows) or `start-evolve-charts.sh` +(macOS/Linux), or run `uv run evolve-charts.py`. A page opens in your browser; nothing leaves +your computer. +""" + +import os +import sys +from pathlib import Path + +from streamlit.runtime.scriptrunner import get_script_run_ctx + +HERE = Path(__file__).resolve().parent + +# Under `streamlit run` this file is executed inside a script run; started directly, it relaunches itself that way. +if get_script_run_ctx(suppress_warning=True) is not None: + sys.path.insert(0, str(HERE)) + from evolve_charts.ui import main + + main() +else: + from streamlit.web import cli + + # Streamlit reads `.streamlit/config.toml` (theme, privacy and upload settings) from the working directory. + os.chdir(HERE) + sys.argv = ["streamlit", "run", str(HERE / "evolve-charts.py"), *sys.argv[1:]] + sys.exit(cli.main()) diff --git a/evolve-charts.py.lock b/evolve-charts.py.lock new file mode 100644 index 0000000..d0e408f --- /dev/null +++ b/evolve-charts.py.lock @@ -0,0 +1,1346 @@ +version = 1 +revision = 5 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[manifest] +requirements = [ + { name = "pandas", specifier = ">=2.2" }, + { name = "plotly", specifier = ">=6" }, + { name = "streamlit", specifier = ">=1.65" }, +] + +[[package]] +name = "altair" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/de/9731304fae332b5783fdc3ed18531dfaf4a3cc62a66aaaa74c047355b70f/altair-6.3.0.tar.gz", hash = "sha256:fa632121aca6d0fcb198a3d8a9f7d937fa5b7518ec947951cfffb44e736d97c8", size = 766362, upload-time = "2026-09-15T06:38:03.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/b9/10a8bb13a0462e0bc4e02c0b7b8237dc24ab590f5bb4be94bec5e8f1d532/altair-6.3.0-py3-none-any.whl", hash = "sha256:7defb6ca730676dfc99a299768e2769f51585fcb3dc960ea71aacc368929d65e", size = 797192, upload-time = "2026-09-15T06:38:01.791Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/67/6a0b94a7960d5e1b5eacd2fb529f3fccc47db4644f7f0a7cfdcfc3be578a/charset_normalizer-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6", size = 370642, upload-time = "2026-09-30T04:35:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/fb/94/01009e13b94041599004edf32e56e382c24e570f60f79bab8efe45cfe1eb/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5", size = 259280, upload-time = "2026-09-30T04:35:08.448Z" }, + { url = "https://files.pythonhosted.org/packages/66/85/3b5358f60a13210f0b67d3755c168ef758701b021e655d88d4da28554467/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74", size = 245335, upload-time = "2026-09-30T04:35:10.104Z" }, + { url = "https://files.pythonhosted.org/packages/74/75/77c1c479b09ecd751d1e767b251ea5c14d4d50ff757bf404afab2692f600/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab", size = 291707, upload-time = "2026-09-30T04:35:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/363f78cacb70f58f15f4b083961bbd9d292f335d3f5c66fc4f1cfe69cb90/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c", size = 286747, upload-time = "2026-09-30T04:35:13.022Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ed/cf505d3011ffceb12c2067a7a5d3cfe92b875d4d44bb0ff0d69375e2c184/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f", size = 269972, upload-time = "2026-09-30T04:35:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/15/d8/f0a93a431d170e7ca681d4f6650fee3de934d18560e474e7267eb4b0f987/charset_normalizer-3.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288", size = 269339, upload-time = "2026-09-30T04:35:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/9b2bd1c5b7af02462c9752d33994834ff972a96b4c483eefde9e594488e2/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400", size = 261683, upload-time = "2026-09-30T04:35:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/a5/cac540ab0fd61f3fec88ad3dbb64509e71424593d73cfdfff5ab3e4db279/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd", size = 248143, upload-time = "2026-09-30T04:35:18.849Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/ff467301deef2089fad87f72df9e000a26a78fec7acbb18e1999371b8369/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37", size = 292358, upload-time = "2026-09-30T04:35:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/22d7e785d1e210afc2e2f58600dd1799d17a35665faf84383f002826c5f8/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac", size = 268766, upload-time = "2026-09-30T04:35:21.72Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/e8e946267f1c2d9e2bd651726e2fbd2addf02c4d36cea5069e32ca9d7bb5/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a", size = 287432, upload-time = "2026-09-30T04:35:23.273Z" }, + { url = "https://files.pythonhosted.org/packages/4e/88/7561d8a88d555e7df6623abe7c0070b4baf47549b9408783a2ae0a1a6cf7/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640", size = 272470, upload-time = "2026-09-30T04:35:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/35/7e/578c702301ec036f01455f30744a08d2b42f6ab35b9b2d4bf8cae0ef2a80/charset_normalizer-3.5.2-cp311-cp311-win32.whl", hash = "sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d", size = 186688, upload-time = "2026-09-30T04:35:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fc/fdf8cf52ff21cd5bf158f20978991cf985325842f74283eb6df26c8a39d8/charset_normalizer-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96", size = 214932, upload-time = "2026-09-30T04:35:27.796Z" }, + { url = "https://files.pythonhosted.org/packages/97/66/3e45a506d8110b632541faf9a9470185aa9878f1ed44020f31346c1c5e5b/charset_normalizer-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1", size = 202828, upload-time = "2026-09-30T04:35:29.259Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c8/693809898870237d82785a03f3b2b58fe4c9f14669f84a7d4e623c92a59e/charset_normalizer-3.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491", size = 367780, upload-time = "2026-09-30T04:35:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/c9/87/2fea8c13dc24b3ca9c6f803a5b2dfdeae73eb4f9e12c7885ed908ff0433c/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c", size = 246730, upload-time = "2026-09-30T04:35:32.286Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9e/09efac30b937722f46d3110ba30b875b24b2e3a266ed746cc4e376a94d80/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0", size = 237707, upload-time = "2026-09-30T04:35:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/9e/18/70d76670b13686237863a379928d60bd10e021f17d243ab3d7014c4a5f4e/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51", size = 273050, upload-time = "2026-09-30T04:35:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/54/e2/77a8b09d5adc013ed07b95b01b8b8fa5441c4e810e83ee7e4aae2fa4d91a/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5", size = 270345, upload-time = "2026-09-30T04:35:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c5/38806a25ab5e65fc178f39affeda20858efafede2fce1ffc2556cfc9fe73/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649", size = 257601, upload-time = "2026-09-30T04:35:37.919Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/213565184708fdb263ae55e2c04ee1ff748129dd65d48ed0e3502da9c85a/charset_normalizer-3.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e", size = 252222, upload-time = "2026-09-30T04:35:39.544Z" }, + { url = "https://files.pythonhosted.org/packages/7e/24/76d2cefc25472531e4c5c7dfff68865eb1c39b78482f0fdc15b46f047830/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346", size = 248482, upload-time = "2026-09-30T04:35:41.088Z" }, + { url = "https://files.pythonhosted.org/packages/7d/dc/65a801b66ab4c197e22c433ab25e7ac24324ac6f45a2269aca42cce309bf/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1", size = 241206, upload-time = "2026-09-30T04:35:42.59Z" }, + { url = "https://files.pythonhosted.org/packages/a7/95/ca9b5eabde673002c6f1e7ada1b223916fe18f6d661da7aabd4d643718f1/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875", size = 273190, upload-time = "2026-09-30T04:35:44.347Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/803b4d2a3f6e1740f63f1e87b04d14b42f3d4fdfe6ed7d4db2d34102b14f/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1", size = 253527, upload-time = "2026-09-30T04:35:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/55/93c0e5dbd085ae0471346026abbe7e0db9ea2d6fea74e51f0b5a46f233a7/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413", size = 271285, upload-time = "2026-09-30T04:35:47.49Z" }, + { url = "https://files.pythonhosted.org/packages/95/69/0dbd0e0b9b16cfa816cdfcb3e2e3854a1f680dc07fb1245ea125e7448060/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869", size = 260010, upload-time = "2026-09-30T04:35:48.996Z" }, + { url = "https://files.pythonhosted.org/packages/58/9d/e7b88e7b1bf403590c3b573277b5e1e488c68c7a6fbacca310a2c324e90c/charset_normalizer-3.5.2-cp312-cp312-win32.whl", hash = "sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e", size = 184125, upload-time = "2026-09-30T04:35:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e6/e6e083884cbcfd49c64865af05027fe7011be7b2d9179524f099a1b611f3/charset_normalizer-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc", size = 207486, upload-time = "2026-09-30T04:35:52.194Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e3/017aea0911ada7405a825c7d937eb3a13009664e2f5b38e8c4bbf2abf894/charset_normalizer-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3", size = 196734, upload-time = "2026-09-30T04:35:53.636Z" }, + { url = "https://files.pythonhosted.org/packages/c5/34/68292d68512768591aaff07c59bb53ee31341c87759433a859c4641a50c2/charset_normalizer-3.5.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5", size = 225946, upload-time = "2026-09-30T04:35:55.313Z" }, + { url = "https://files.pythonhosted.org/packages/e3/80/bee0b01b90ccd5322ae1d0abb33fab1bd95b7c2eadaf02aeccf22e04ee83/charset_normalizer-3.5.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e", size = 238619, upload-time = "2026-09-30T04:35:56.863Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/60ce52a85a7fd631ae8482ae6d74521014ca2f255892679484dc04d7ef56/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a", size = 206701, upload-time = "2026-09-30T04:35:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/36/8c/71aafad23f971afc84c2b295bc0c560739ce1dac558aad9fec22e39f3639/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d", size = 210506, upload-time = "2026-09-30T04:36:00.147Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/3c5a7798c046df7d2d68ad653cf5b6c5a8bfee225055a843c6f2f42aac1a/charset_normalizer-3.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055", size = 366626, upload-time = "2026-09-30T04:36:01.77Z" }, + { url = "https://files.pythonhosted.org/packages/e1/16/710ac3de2ee354e2bd1a9c94efe45a2d27b5c6ad39b2d6a905be2c094b6c/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858", size = 244624, upload-time = "2026-09-30T04:36:03.389Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/45c7439f5b63d24f7d5b2a1d760f34af7628782d7144b4cc8ded45c2d4bc/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234", size = 235731, upload-time = "2026-09-30T04:36:04.987Z" }, + { url = "https://files.pythonhosted.org/packages/4d/34/38f3154785ce92e9f56eb226f4d35bdfae6b008480dd055f58837a89c810/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21", size = 270352, upload-time = "2026-09-30T04:36:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/04/f3/859f74e7babc977705026b30593b3be04049632a522fb7000f83c033d747/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718", size = 267393, upload-time = "2026-09-30T04:36:07.865Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/41d27f234b82e47c167a5f6c0f62501dc0c640585ff4aba79e08a390336a/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4", size = 254660, upload-time = "2026-09-30T04:36:09.248Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/5d1a997587febe5b26d8daffe363b5c1a091cece19828eec6502fd09c5ef/charset_normalizer-3.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3", size = 249996, upload-time = "2026-09-30T04:36:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1f/d1e78246f7ed60c8c8d606b4ac27f66ce49cc3e95f24893ccbeba9f77302/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c", size = 247028, upload-time = "2026-09-30T04:36:12.294Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/eba316edd4f0c4d3a5d945924c4eeeae59abac4056aa815d8a4268f863a2/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429", size = 240562, upload-time = "2026-09-30T04:36:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8e/aaa037d40ca9ef045977f1a661048b1aa33f223adfce3452fe9be9f79d14/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f", size = 271699, upload-time = "2026-09-30T04:36:15.41Z" }, + { url = "https://files.pythonhosted.org/packages/26/19/1c1c9f75974adf523b87f34b8a2adc5a435cd65916812bcbd0dfa45f9a29/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a", size = 252030, upload-time = "2026-09-30T04:36:16.839Z" }, + { url = "https://files.pythonhosted.org/packages/bc/90/0660ef18e18df0a4d2a1a0edff7dfbba42d4e50ef2425557a5bb7051f77b/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00", size = 268565, upload-time = "2026-09-30T04:36:18.468Z" }, + { url = "https://files.pythonhosted.org/packages/79/ba/57adc269824e8658f1a0f97a9e514c247445a9632b3419b97e0ba37f16dc/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d", size = 257627, upload-time = "2026-09-30T04:36:19.938Z" }, + { url = "https://files.pythonhosted.org/packages/9a/85/33abd4315c052d3d4f54c92b1ee49bfbc0dc7115a981e462a793b6d2ab87/charset_normalizer-3.5.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3", size = 143407, upload-time = "2026-09-30T04:36:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/4f/de/6435e18d1aaa5d910b896d551411c96af1f42a0c56c29afc2016c61ccc2e/charset_normalizer-3.5.2-cp313-cp313-win32.whl", hash = "sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd", size = 183207, upload-time = "2026-09-30T04:36:22.776Z" }, + { url = "https://files.pythonhosted.org/packages/9c/76/b8ec57f4e9ee3253541abf95e4a462c0175fe8032dcd070f1f2421240942/charset_normalizer-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639", size = 206489, upload-time = "2026-09-30T04:36:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/c647c6ae47480221e875ea5d743ff94946f7416e3c69415ab772928e8d32/charset_normalizer-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3", size = 195501, upload-time = "2026-09-30T04:36:25.846Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" }, + { url = "https://files.pythonhosted.org/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" }, + { url = "https://files.pythonhosted.org/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" }, + { url = "https://files.pythonhosted.org/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" }, + { url = "https://files.pythonhosted.org/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" }, + { url = "https://files.pythonhosted.org/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" }, + { url = "https://files.pythonhosted.org/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" }, + { url = "https://files.pythonhosted.org/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" }, + { url = "https://files.pythonhosted.org/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" }, + { url = "https://files.pythonhosted.org/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" }, + { url = "https://files.pythonhosted.org/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" }, + { url = "https://files.pythonhosted.org/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" }, + { url = "https://files.pythonhosted.org/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" }, + { url = "https://files.pythonhosted.org/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" }, + { url = "https://files.pythonhosted.org/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" }, + { url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[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 = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, + { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/55/18dbb4778b30ada5ce071608503cc3edc9e14e13d868c17a6d178fc30f7a/markupsafe-3.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346", size = 11520, upload-time = "2026-10-02T23:04:36.299Z" }, + { url = "https://files.pythonhosted.org/packages/6c/14/0b05f79b4733e264a18d08fe08fa1df7347630ff32a6cb82180d9dccec55/markupsafe-3.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91", size = 12063, upload-time = "2026-10-02T23:04:37.291Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3a/63ba10b6c1463216b3e4df669a9f0e5a3b0c3071557d2e8229e3968c79fb/markupsafe-3.0.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef", size = 24321, upload-time = "2026-10-02T23:04:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/5f015261b76ad633d187ef6f388b413aedd64a8773c4df59e530a0be5525/markupsafe-3.0.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169", size = 28845, upload-time = "2026-10-02T23:04:39.295Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/26e594b26be40c2f1fec63ccf8a8b99d0335a5b2cbe84835c7d82a994375/markupsafe-3.0.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb", size = 24701, upload-time = "2026-10-02T23:04:40.348Z" }, + { url = "https://files.pythonhosted.org/packages/81/a5/a513b76c139a3915b43404324e55c0b7979ae4f0d39eb6f075b0282e90a8/markupsafe-3.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808", size = 22961, upload-time = "2026-10-02T23:04:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/46/cf/4c66192c100b4542bcbe392ae06696b670f66927be3ac38a213234778ff9/markupsafe-3.0.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692", size = 21920, upload-time = "2026-10-02T23:04:42.429Z" }, + { url = "https://files.pythonhosted.org/packages/cb/17/ac3662678bfbad649893117ada2ba44dc30bf56884e84f13154a792b10f1/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d", size = 23726, upload-time = "2026-10-02T23:04:43.531Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/fe47cee339180a69ebca3c57fb3483d0f5cbd1e8337d1871fb1d9c1aebee/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21", size = 22539, upload-time = "2026-10-02T23:04:44.618Z" }, + { url = "https://files.pythonhosted.org/packages/12/32/d55440ba140442800e02d799c9cb5ab597bf6ebdb1177b5ea39a11f797bd/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707", size = 23963, upload-time = "2026-10-02T23:04:45.656Z" }, + { url = "https://files.pythonhosted.org/packages/50/9d/9c86042cb364c2ad4c971e6d1247929effd25f714cd7ee11b05e6316445b/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e", size = 21506, upload-time = "2026-10-02T23:04:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/75/ef/5b824f03ba40c3b3652b6272d083d2fc4fcdd440de519a3a39ba2c3e7262/markupsafe-3.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7", size = 22955, upload-time = "2026-10-02T23:04:47.698Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e8/44cfcb5ea40e5e43cec7793ef90704ed0c475280839076c4345757eb8e59/markupsafe-3.0.4-cp311-cp311-win32.whl", hash = "sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5", size = 14094, upload-time = "2026-10-02T23:04:48.788Z" }, + { url = "https://files.pythonhosted.org/packages/91/89/f2b509f7bf79352e40117824c1070dbeafd4df67031d3fa98165a3134228/markupsafe-3.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3", size = 14328, upload-time = "2026-10-02T23:04:49.801Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5a/ccf22672a0f64dc682306e288f0dabcb06c7a201f3bb6e6cbf86d9e8ad03/markupsafe-3.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e", size = 14188, upload-time = "2026-10-02T23:04:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/81/09/4c59d56b8461ae8eb0d8ba34bb25b7e618547044679d58a82ef9b2479fc1/markupsafe-3.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6", size = 11658, upload-time = "2026-10-02T23:04:51.876Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f0/d6613774d86fbf6d145751d43c59875e47a6f9f17daee0aef173bd36d90e/markupsafe-3.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f", size = 12050, upload-time = "2026-10-02T23:04:52.931Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f2/8f18e0b806eb13c1f8d07d917a720831ead54253a6dec011fbc78098a6f8/markupsafe-3.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b", size = 24363, upload-time = "2026-10-02T23:04:53.895Z" }, + { url = "https://files.pythonhosted.org/packages/60/ce/fa07dbe8a5675558fa36dea033e19995bc783de2dec5f540ccb9030b06aa/markupsafe-3.0.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df", size = 28525, upload-time = "2026-10-02T23:04:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/85/40/be87c01f3868ec217f8a2015089d71c22c8c5a75324822e5ed1cdd87210d/markupsafe-3.0.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c", size = 24733, upload-time = "2026-10-02T23:04:56.229Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/aeedb5140afa41fc74c225e9184ab96723a6e873b6ee1c9fede7283456d8/markupsafe-3.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581", size = 22985, upload-time = "2026-10-02T23:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fc/e91352bb08c6a59da3ef0909d457bf95a5f5908fbf151b30a06d9dbcfbb4/markupsafe-3.0.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77", size = 22001, upload-time = "2026-10-02T23:04:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f8/bffee5e7d2a3deb59748a797650a48af7e672025cf641a79344a771ad106/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c", size = 23793, upload-time = "2026-10-02T23:04:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/ed/59/b853d6628ecb4d658e1d637224846d5e9bb4adf4f8df97f3be9f29dce2ec/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749", size = 22640, upload-time = "2026-10-02T23:05:00.768Z" }, + { url = "https://files.pythonhosted.org/packages/09/b2/1506df394f0f075797c418d0301498f49e43be194e3ffcb49e6fe6ccf022/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed", size = 24074, upload-time = "2026-10-02T23:05:01.813Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/5ed69cda630ac69ef60d06c09ba5a7f84ff66a2e28cf986fd5614ab3c6e6/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786", size = 21563, upload-time = "2026-10-02T23:05:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fe/fb1e79be0fea60aa32602ebefc9c35a82bb42b4df157285ab7dfec12341a/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e", size = 23048, upload-time = "2026-10-02T23:05:04.479Z" }, + { url = "https://files.pythonhosted.org/packages/c8/52/7632a53360671a9b750cdbabaf9cdd89f18b42248b8e4cb42c0b0296e459/markupsafe-3.0.4-cp312-cp312-win32.whl", hash = "sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237", size = 14108, upload-time = "2026-10-02T23:05:05.513Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/62495e180b7000aaf30000fff849e933f74264638057176cf46852500adc/markupsafe-3.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7", size = 14303, upload-time = "2026-10-02T23:05:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/4c24208776a65878d656996945aacfbfe010d3720d1a98fc0eb8491fc03b/markupsafe-3.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9", size = 14174, upload-time = "2026-10-02T23:05:07.617Z" }, + { url = "https://files.pythonhosted.org/packages/6d/18/4bc5ba32499e87bb2b0ef5b3a9bb9c00a131fa961ddf0be548cb550f548b/markupsafe-3.0.4-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", size = 12786, upload-time = "2026-10-02T23:05:08.709Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6f/17f0c099bf25f3e31e63cc19244d9f6af861a9a4ab778c203997903cfdd0/markupsafe-3.0.4-cp313-cp313-android_24_x86_64.whl", hash = "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", size = 12510, upload-time = "2026-10-02T23:05:09.93Z" }, + { url = "https://files.pythonhosted.org/packages/11/af/1a141081b905036ee904ec4bd945e1f70b4e1b32d33c4e59e8cf1d58b247/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", size = 11583, upload-time = "2026-10-02T23:05:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/a89385ae590232622a03e091805cff12f24fabe6c11e0e8bae096cece81c/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", size = 11801, upload-time = "2026-10-02T23:05:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/ed/85/ea548dc013962eb73653124bc595635fbf9e0fa41d1f181a967ccb784dfb/markupsafe-3.0.4-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", size = 11443, upload-time = "2026-10-02T23:05:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/cc/72/15f2e5ec9cf2eb00d5cdfe968d94e4156a7bd7303832c3f3b2c403a36839/markupsafe-3.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", size = 11665, upload-time = "2026-10-02T23:05:13.829Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e0/4030bea613677e333c8a2c901fd405055f657f9d06acba5b7357984b6ef7/markupsafe-3.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", size = 12061, upload-time = "2026-10-02T23:05:14.807Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a5/28b76a7449eb702966b88bef599e2360b411fbb3afeee8fe560939be06ec/markupsafe-3.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", size = 24395, upload-time = "2026-10-02T23:05:15.909Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/21232811afc3a063b5e934b1ae2efda52f46154ec382f585149c020e61fe/markupsafe-3.0.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", size = 27183, upload-time = "2026-10-02T23:05:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/38/6ccdfa5b59049cb36fb80cbc80aee9cf1fc9bb77d1335ad435f2070b08cf/markupsafe-3.0.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", size = 24772, upload-time = "2026-10-02T23:05:18.209Z" }, + { url = "https://files.pythonhosted.org/packages/63/e0/cec6865dfe88cb48fedd4b20aed6af5158e41092adcbf3e028bcc6ec2108/markupsafe-3.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", size = 23017, upload-time = "2026-10-02T23:05:19.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/76/6ed4940bb7648a9aac457c14f870cfdd5105f139a0fb1f29cd61fafa47d1/markupsafe-3.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", size = 22036, upload-time = "2026-10-02T23:05:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/ed476226d4fe46a09090a36025bf319296810028df55eb12f1253b540f3a/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", size = 23786, upload-time = "2026-10-02T23:05:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/9a/35/66ff30450e35ef5fba9ebc930c9411747e537fd9447b65e44f5007e2b84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", size = 22672, upload-time = "2026-10-02T23:05:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/32/0b/72f45ce4b4efcbca4b80cf1b06703eff0be8d37e82abb78f66c85a7ead1e/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", size = 24107, upload-time = "2026-10-02T23:05:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/71776e5fdcba04614b384cc102e8a4198208579d896fd1394cb7cb9aa900/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", size = 21615, upload-time = "2026-10-02T23:05:25.215Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/801ce02a02e7aee0f784b1ec7843026178f6adeb9c93ac67eb1992a9a84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", size = 23076, upload-time = "2026-10-02T23:05:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/c43776625428f3bb4a61e8633940400e3efe6409e3c6f5bff26de5e45618/markupsafe-3.0.4-cp313-cp313-win32.whl", hash = "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", size = 14118, upload-time = "2026-10-02T23:05:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/6f/36/163da64de88a13db79214ef75fa041be7fa13bdb42261cf5b7484de14bfb/markupsafe-3.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", size = 14313, upload-time = "2026-10-02T23:05:28.749Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", size = 14189, upload-time = "2026-10-02T23:05:29.917Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z" }, + { url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z" }, + { url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z" }, + { url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z" }, + { url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z" }, + { url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z" }, + { url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z" }, + { url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z" }, + { url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z" }, + { url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z" }, + { url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z" }, + { url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z" }, + { url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z" }, + { url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z" }, + { url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z" }, + { url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z" }, + { url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z" }, + { url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z" }, + { url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z" }, + { url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z" }, + { url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z" }, +] + +[[package]] +name = "narwhals" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970, upload-time = "2026-09-08T13:32:08.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034, upload-time = "2026-09-08T13:32:07.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, + { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, + { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, + { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, + { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, + { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, + { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" }, + { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" }, + { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" }, + { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" }, + { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" }, + { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" }, + { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" }, + { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" }, + { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" }, + { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" }, + { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" }, + { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" }, + { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" }, + { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" }, + { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/17/d7b106e05bfa642e8694451e7d3d759c6a241c5386a5d962e4f66c047e06/pandas-3.0.6.tar.gz", hash = "sha256:66b07ef7315a31bfe1089cd3d71a7de781c9dca986762d0b4fe7c0ef17465d10", size = 4667686, upload-time = "2026-09-17T23:23:18.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/48/88e8d250d28efa8163294f6809a71683c7ee67f63ac7c33021c0503b3547/pandas-3.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:085e3786ae6b2e82b406266bce36690f72b9dc1421903ba9296b2981a9fcf586", size = 10391798, upload-time = "2026-09-17T23:20:20.96Z" }, + { url = "https://files.pythonhosted.org/packages/55/a6/39db5d41f3eb5d7846626312cb30e0cea48e988fc47a3725698bc931ad3c/pandas-3.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7564d86a94c2eb8ab290b07f63ddaae5c032fa53897c29a2ff2197d43aee8af", size = 10023718, upload-time = "2026-09-17T23:20:25.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/b7/707e966129f77ee8a39d41a41bbd989b790b3fef581c6e26b821315cba6b/pandas-3.0.6-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7c0afdcaf6661d795fcefc2f647ddd1136f62cdc153fba177c685d97a87808", size = 10612800, upload-time = "2026-09-17T23:20:27.99Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/dfb6cc9329d0bbe76dadda8a1113d026bb996ec76c509221368dc68349f4/pandas-3.0.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47121f9571503f724c9b93e297ab6254ac99c77adf5e9ed085ea419fd585c258", size = 11108900, upload-time = "2026-09-17T23:20:30.65Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6f/3d58f15bbe972f3d7bfa13ee06d731785a76fe8d232c92b2655a8de14127/pandas-3.0.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:994a79608263fe1c14cc48ffa7300e2b834b7d1cb406ffe96a08828cb0cdd79b", size = 11630226, upload-time = "2026-09-17T23:20:33.582Z" }, + { url = "https://files.pythonhosted.org/packages/58/54/9b494de4a3dd92fc6eb19187db1b21afb50fdc21962f32ea1aca9f270cb2/pandas-3.0.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a3a22e07fe75347eaacc75b0e85297947af4fba6b4aae23916bd8b6828d0bba3", size = 12146942, upload-time = "2026-09-17T23:20:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/d2df02854aec5d47659acfb2be352eecc691845b2f86e99c84f1010a8671/pandas-3.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:2e5fa32ff162dfdbc280157d664f44d23049ae414725af9676df339c501d82cd", size = 9859246, upload-time = "2026-09-17T23:20:43.976Z" }, + { url = "https://files.pythonhosted.org/packages/79/1e/2a30df0d7dede5c195300a1820b0bc21cea3aa24e4c8b6c4431565ecc79a/pandas-3.0.6-cp311-cp311-win_arm64.whl", hash = "sha256:5e75072773c1b2f7cb63faa3a6f562aede11f3976f68ed34cb538bc091a28171", size = 9106923, upload-time = "2026-09-17T23:20:46.611Z" }, + { url = "https://files.pythonhosted.org/packages/77/4c/597d588c055d4373cff19cbbc32d4dd046c7be8fadee957585b5ba9e5b24/pandas-3.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dac2d65e9087e8e7b5a45fe15c4920911a221df061ab629943ce016489145c7", size = 10397411, upload-time = "2026-09-17T23:20:49.465Z" }, + { url = "https://files.pythonhosted.org/packages/18/8f/48907c7c707b61a8e5018c32e1a3f70623209bfb59020a2f6196159d3aa7/pandas-3.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9dab635a549e58a053c7b0fa054dc0bd7be22f0ed9a720f4a85d5fb993276172", size = 10051279, upload-time = "2026-09-17T23:20:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/67/fa/613d867c3d9554a61bafdec6f79565c8a3e73235feb52cc4a72ad2e0fa6a/pandas-3.0.6-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3dccb584123b399c07562ac4d62543e90ede49ddf8ce3c13ffc64cbe828c281", size = 10303429, upload-time = "2026-09-17T23:20:55.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/67/0c0f18e38d7f2d2af8c24b3315bc4046e73bbdd4a5540405506671ad0c0d/pandas-3.0.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0704044b676496b8350e023b09f174a26772456c974a2b11c36bebb558c9490d", size = 10788193, upload-time = "2026-09-17T23:20:58.617Z" }, + { url = "https://files.pythonhosted.org/packages/39/53/1b57f3162501fe36687e4870e1b918a6458ca7386b173af75663ace95857/pandas-3.0.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e7c1905ef02c3d6d43d9dbd5b6ccb4da4870a0b0c821bbc103fbdb6f3ad2707b", size = 11323501, upload-time = "2026-09-17T23:21:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d2/b1182e8d39100369d7f13f4a125a3fb6b096fef112c46d0566c25781ff68/pandas-3.0.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:569e114072b24fc4970c12e2b4bab252671668a40b324318903380cab0254c0c", size = 11838173, upload-time = "2026-09-17T23:21:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5b717af24d2f27995e51e0875a269dddd373045216e34cf62e3aa764eaa1/pandas-3.0.6-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:2a8fc94be2ee5f1d86f97aacd8cc566f81680b6498e76f3007421bb5d98151bf", size = 7118499, upload-time = "2026-09-17T23:21:07.661Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/14b3b17cd75cef4a1ee1af4234eb41cc1afe4103b98c2d80e8916abfd42b/pandas-3.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:3ef908d28590b3f42d7070e7ad8f9b34b442b260b7f3c1afb57e0040c58cdb1b", size = 9661439, upload-time = "2026-09-17T23:21:10.959Z" }, + { url = "https://files.pythonhosted.org/packages/3b/11/3d580a604a1e35d69f6676847bd12db7d14344bc677b717f4413e79c5d0d/pandas-3.0.6-cp312-cp312-win_arm64.whl", hash = "sha256:f4e7c52eb108d752e7592268108fd3e98efd76d83a3125cdd06c621c2e44359b", size = 8959806, upload-time = "2026-09-17T23:21:13.851Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/143605a1f6443ad50ebda78a31e5a3a10147fec2590e931584aaa5ff0a09/pandas-3.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ae8073aed8e21d1a7fe263dcdc6840743549722a6738198a0a46000fa9476f2", size = 10418900, upload-time = "2026-09-17T23:21:16.594Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ca/87f8548f73d452aab35e4a90f8b39ae303295e0f2ef0b4055c44d6b3f1be/pandas-3.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:60d81f9e1799b36f3739e7fff44d1fbb2e8fd5a271b3863e03de9715fccda0fa", size = 10064785, upload-time = "2026-09-17T23:21:19.677Z" }, + { url = "https://files.pythonhosted.org/packages/43/1a/d951442e5607c6e3b2462eff8f420797d428aa74b87c6ecfe4f48553626e/pandas-3.0.6-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:097090508a1dd335013d39106fc10b20f4fd4a171638e47b77d55798ed9dab6c", size = 10245290, upload-time = "2026-09-17T23:21:22.797Z" }, + { url = "https://files.pythonhosted.org/packages/50/fa/96d50e1e6cd0b08b5e2b7c838f65ae644940f75a124063380b5ef73b6866/pandas-3.0.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e92d9fa834c7d877130027cddc0cad8dcff97c1f6cca26bd6310f847228b658", size = 10757657, upload-time = "2026-09-17T23:21:25.673Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/f82d13a2cb703e1a8acee7e01fdc2b898d9cd0c00f07d1dfce63af43e350/pandas-3.0.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b27c8d890e4aa2171437ae2a39de1d215e674158e4865c4023a8b31c932513b2", size = 11249114, upload-time = "2026-09-17T23:21:28.898Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ce/8aef2e561a2f2c8b38c913c67373c65ba6748174e763d27c80271b24bd17/pandas-3.0.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8029ec0f1f89e4f985929ce1f6626dabf3140d61a4e9c1215afdab34eaf9a5d", size = 11820511, upload-time = "2026-09-17T23:21:32.11Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bd/63cb67e6903ef6d9c2871916dbcbc09d254da0fe8b870cf62e16b21945f2/pandas-3.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:f3ce8a6968045481e91a3990e797e348ce13db45ee164a7095bbc824e26c09dd", size = 9638092, upload-time = "2026-09-17T23:21:34.883Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/e7b35b712edb068d382ddc8b2bea8a04974100515ba2daa22b478b265842/pandas-3.0.6-cp313-cp313-win_arm64.whl", hash = "sha256:cc39303913e2ea129915670de5d1c9fbd647f543bb72e5543bac8baa94e9e42f", size = 8952032, upload-time = "2026-09-17T23:21:37.729Z" }, + { url = "https://files.pythonhosted.org/packages/75/55/1a8875395b05ccd572cbca0b9255dcd2db6e6508e632a558c1a6884b39ad/pandas-3.0.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ee913a91669056c1de1a6b733fbfeab711de9e54e3bee2dfa5fe79d9457247d1", size = 10492213, upload-time = "2026-09-17T23:21:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/35/61/47ae13476995cc8a40cd609e93e7cf11f273d8692925c2903cb6d38aa0d1/pandas-3.0.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ff51a4459ed036e93d1eb1bb5e6e7b28685d3cb6b7c12b91c05b31024e234729", size = 10156618, upload-time = "2026-09-17T23:21:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f2/cc5f2adb8d6e86a85d9fb5128f8cf205a61189336f70d1f7faf0d1b53ec9/pandas-3.0.6-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654aae059295dbba6ecd2328ca12712a2cf1676214c8699f1c29213f7ccf9c34", size = 10375479, upload-time = "2026-09-17T23:21:47.159Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/ffdcb19be4ff6bfe7d969e7cef2c567c633df5a3a1cc1053394ad053bca8/pandas-3.0.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f51d7f651c8054c5e82a69265c98082e795d1442df7ca6edc3a545d61214b1", size = 10783244, upload-time = "2026-09-17T23:21:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/77/5b/e150075b2c6eb69fae896f2d9239bc6ed07db97735971d53d66de6553460/pandas-3.0.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22172a92e7ee678ec0140c7af4fc9366b55413834a1cd86af78b3caa0b0574de", size = 11382223, upload-time = "2026-09-17T23:21:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/b441c587dc7355bf6e1f68a91b4f76a6c29740f0c23be3acc5d4ebbeea6d/pandas-3.0.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583be68728a31d0d750d5b8d9e00f02b153df0d4655f858bde93cb84cfc4227c", size = 11854881, upload-time = "2026-09-17T23:21:56.342Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e9/f43410fada510b43fec09993c08f552086c3d247d3ee801a678f3cb10ea5/pandas-3.0.6-cp314-cp314-win_amd64.whl", hash = "sha256:77ccbe5057aece6fc172b9b77f19c04335af6882bc2e10c8f3ee4e6bfb3da553", size = 9791672, upload-time = "2026-09-17T23:21:59.332Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/db14c059c21f9baa1907d436f8bf30e0c76c6288225c5e8b79a08ba8b2c5/pandas-3.0.6-cp314-cp314-win_arm64.whl", hash = "sha256:fb625f426b375bcc96e3a04c5d5d266cd7be6ae5d6866e0e703382ab5164068c", size = 9121246, upload-time = "2026-09-17T23:22:02.123Z" }, + { url = "https://files.pythonhosted.org/packages/67/ba/bad0f8dac020ab38a8637fddab01a57a82da7a496a6e6f19590aad53ab62/pandas-3.0.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9e492cd4bdba6778de4fe0df7f4590c012161ebcf9902dce01b01dc683105514", size = 10920612, upload-time = "2026-09-17T23:22:05.404Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a9/b500982e9aac6d52a58da4ad3f11e14168a315b06906b3f397c427878065/pandas-3.0.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7dcd21238cbb4828ff148481ba01cac8946dc5121457b5aeba28636f8f99a60", size = 10570213, upload-time = "2026-09-17T23:22:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/e6ecd0073c98be8f840ac3125b955272835d7d9fd69f5944383b164deb5e/pandas-3.0.6-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff482fa91fa2bafd92e8fe66ce3645c851824310f295c1f0a2f96e928fc4541", size = 10252356, upload-time = "2026-09-17T23:22:11.302Z" }, + { url = "https://files.pythonhosted.org/packages/04/f5/001e230a7a7803590d9275a1a3f7e1bb605e3a495cfe5e8d3a532090621b/pandas-3.0.6-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:db7ec631f26223beee8e5c9e0b8f23c24d8197bbd1d982421d4e3188bea51965", size = 10655952, upload-time = "2026-09-17T23:22:14.283Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ab/bab587148a3852c96aae26c4b5f9e04ce2221801ad94e166b4fbf969ede0/pandas-3.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd75ed0c840f709fc2ae26ddd9534ac77ca1a48ac0cce521a74acaa85f3340a7", size = 11274177, upload-time = "2026-09-17T23:22:17.352Z" }, + { url = "https://files.pythonhosted.org/packages/f3/32/74b48d87df2b80892d713c149abfe36d5db4de41b4eccb042a2bc07dafc1/pandas-3.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ef738d71d1059245b6bb03e312be06d8b3821326a83486c1ad03b9aba3710e44", size = 11719041, upload-time = "2026-09-17T23:22:20.227Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c6/d64b72d64d7eb0fad9fe424d34138e45dee70ddbea1360dcd0adf30e28f6/pandas-3.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:429d9df32731ab01383ed98f2baa7a60368090d1a94fc06019a12062510e8630", size = 10191388, upload-time = "2026-09-17T23:22:23.524Z" }, + { url = "https://files.pythonhosted.org/packages/7a/30/5e5b2ccabeca73ae2b03fc82bca3eabb7466cf43737f05ac08d591665d47/pandas-3.0.6-cp314-cp314t-win_arm64.whl", hash = "sha256:a4dbd4dc65cbe645b92b8785d0f96dd7311010dc6606cf620e51b07b8788a12a", size = 9387796, upload-time = "2026-09-17T23:22:26.64Z" }, + { url = "https://files.pythonhosted.org/packages/b6/77/47c5fb0be8bdd00116814c2c40d9ec42dbeb943865ef95130fe58a898226/pandas-3.0.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:50c44cbf5820b6b91a5f74aae04972472aefadd3cd9fbd1010409d85528bd570", size = 10483960, upload-time = "2026-09-17T23:22:30.071Z" }, + { url = "https://files.pythonhosted.org/packages/09/08/a310cb2fefe6d2b4623ab2150da818d93d4e73e33b58e4d163bb74243c5a/pandas-3.0.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:eb6900de08ac85f93ac4948aa6b80842eba555875337b8359035ac9c43e92d34", size = 10149432, upload-time = "2026-09-17T23:22:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/54/36/6af478ec3a26d7754555cd62c3101c589c0931b1d85398e4fa5403910a1c/pandas-3.0.6-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e25e2e1adee99ddfada6f7206a79ae8e9c8a8861b0e3eaaba165006d3eef18e", size = 10360981, upload-time = "2026-09-17T23:22:35.621Z" }, + { url = "https://files.pythonhosted.org/packages/43/20/5ece0e9cb79a6473216620db6a90546f578001c2bd857b77c444b27acad1/pandas-3.0.6-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ff44b2cb51cbd691c91f92c4ea6c71e34003f239ebd67c2e857dc898466b49c", size = 10776585, upload-time = "2026-09-17T23:22:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b6/cd3038f31ade5e8d2b4e1c9549d4b31e4d598469562f47142b2ad9171c0a/pandas-3.0.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5edd0a7abb0986ecce1ac81f56d99b6763f86aa6946dceb6c661224f90af5a19", size = 11385249, upload-time = "2026-09-17T23:22:41.18Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/05bb3003737f80375d7311774916a24d161e2e591abe8c672aed7813defc/pandas-3.0.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1bcb3e9ed29e74a7439cedff9e2aefd3ea65de84d7de9ccb6c194192541bd60e", size = 11848595, upload-time = "2026-09-17T23:22:44.207Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/1c0d46acf236d19ef975e9cdd5d1e0dd54924b03f0ed8287096ad4a18152/pandas-3.0.6-cp315-cp315-win_amd64.whl", hash = "sha256:253e12cb9081b0afbac607920f6142975966bc315135e09de275fdbaa415d2de", size = 9783810, upload-time = "2026-09-17T23:22:47.097Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/df3304a9c2833c4810e7f1c887b04105b24731f9deef8b5d5d04522a375b/pandas-3.0.6-cp315-cp315-win_arm64.whl", hash = "sha256:97274c9adf6255bb48c620cd6959805efa7f09ea2167f0e0ae006a448cd2fca7", size = 9116820, upload-time = "2026-09-17T23:22:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/47/25/d5f6cce5efa17c38e4f752a875b4a26d66cfadd529cb8c81672f0376d6a6/pandas-3.0.6-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:265f562fdd1079f69f3de96dd425c3405224038c0af4f920c54bd240ee2c4640", size = 10894614, upload-time = "2026-09-17T23:22:53.223Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/e1876bfdc1df06bafc022d33202b5663bd86c81df2a6140aacafd0344669/pandas-3.0.6-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c6e4aae3e9bea26c6c9a20d88d96c86ec4a99b4db5fd516bcb4e829ab2c0ee36", size = 10546973, upload-time = "2026-09-17T23:22:56.155Z" }, + { url = "https://files.pythonhosted.org/packages/e9/27/e0a27a5a5c27f7db66657b44def9e93121fd0ad4f0808355b9898fcbf204/pandas-3.0.6-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a77a1a44e4d88f1c6a2a64d3eb12efec8420875722e14279800b173a7c7c2804", size = 10239717, upload-time = "2026-09-17T23:22:59.603Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/4eadb7d86a921c1e8bc70916cfe601ed667c4169118d17d69958611c21f8/pandas-3.0.6-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86fa853a12e0b70927e2b1ee00d56d2224ec9cbb4b9d58348b5ad52d2f21150e", size = 10641275, upload-time = "2026-09-17T23:23:02.81Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/eba72e9d905e84e79aefeefdcc6bc1abe15d9077973566643f4211636966/pandas-3.0.6-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c826e9babb7790142c399f58599d8de679bea059d7b39c5b6efa2096fac37266", size = 11262919, upload-time = "2026-09-17T23:23:06.038Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/1c5bd2642b450e374b6191189f47c49538fe41f82348a66de6f647e6ab59/pandas-3.0.6-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8fe77b408d82e2615674dfed62533b95e18a03610573877422aada4f625d4947", size = 11709148, upload-time = "2026-09-17T23:23:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/73/3d/1b142bd0d0f1326a5d98c91c955b923cd1e06b5eecb428cd9d8817fa01c0/pandas-3.0.6-cp315-cp315t-win_amd64.whl", hash = "sha256:83e91d15738d7783c050197cef2f2cf82fc6353dae9865aa87ed1fa16aa4d55a", size = 10161518, upload-time = "2026-09-17T23:23:12.365Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/6419c14da2adc1f09a6a183b8f91d7494d325b287f4ca984ac04f663638a/pandas-3.0.6-cp315-cp315t-win_arm64.whl", hash = "sha256:963ca21199097a84c7827c4678b04e30833084fbf8ef44fde3fa7180a29f8fa0", size = 9364529, upload-time = "2026-09-17T23:23:15.274Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "plotly" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/c3/72b369f5ed7701b04ab0ea3dcf83e9bbce71c0b3bc6f07f87568550d09ea/plotly-7.1.0.tar.gz", hash = "sha256:f860166a4a3d78c69cb1f4a15f28a5c8283eade98a282a698f3bb853a449ace5", size = 6689315, upload-time = "2026-09-15T19:21:21.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7d/905a3a3d51087515719058c94cfbda2ff0fc14417c20d557ae3e82d8b250/plotly-7.1.0-py3-none-any.whl", hash = "sha256:dbb7fa18afce40d0a8e80d1bf162eceb3faa0ce5a77fe741ad09a74cf78f53f3", size = 9692368, upload-time = "2026-09-15T19:21:18.331Z" }, +] + +[[package]] +name = "protobuf" +version = "7.36.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737, upload-time = "2026-09-17T20:07:59.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039, upload-time = "2026-09-17T20:07:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219, upload-time = "2026-09-17T20:07:52.914Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223, upload-time = "2026-09-17T20:07:53.985Z" }, + { url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223, upload-time = "2026-09-17T20:07:54.931Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1b/dcc64f358fcb51811b58ae40b3d28f820725f116d86487cc20bd4b130701/protobuf-7.36.2-cp310-abi3-win32.whl", hash = "sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728", size = 442998, upload-time = "2026-09-17T20:07:55.826Z" }, + { url = "https://files.pythonhosted.org/packages/8a/55/b77bda4e5e5f5971fb51b07663694690e9afdb9402136c16a522bd621cad/protobuf-7.36.2-cp310-abi3-win_amd64.whl", hash = "sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353", size = 456514, upload-time = "2026-09-17T20:07:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806, upload-time = "2026-09-17T20:07:58.211Z" }, +] + +[[package]] +name = "pyarrow" +version = "25.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653, upload-time = "2026-08-10T12:40:53.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/8b/0d23b47702fcfe8b3618d5292035099675c5a1c48258932350c08020f7b5/pyarrow-25.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51093dd9e10325fbdb3c10a2ae7c4806e5c822d94e74ae4938b26524a3323fee", size = 35946180, upload-time = "2026-08-10T12:37:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/17/707d17a5476c55a9541fde0db8213ac30979a792864d72415f176ba50c45/pyarrow-25.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:eb6203482ff3746a5632303a7279ae0b5a304c46985b49ed1378cb350ea6728d", size = 37644787, upload-time = "2026-08-10T12:37:25.795Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b2/cdc98ecf1a6408280bc3a6a07054cdd99a3f4670acc0545d383ce113e87d/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:880523be3d29efcf83d3998835d206118ccf35e3871dbd2fb60408cf6b007a80", size = 46834633, upload-time = "2026-08-10T12:37:33.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/6e/d3fafc41f378b2c65be43b827798c0fae42049a641c8526633ed3eb573e2/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:25f8720bf6387d5dc2ebd2622112de630760419e4b66134405dd24110d15f37e", size = 50065507, upload-time = "2026-08-10T12:37:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/d5/12/8d0698954b8c3001844a898e0a6900bebe83d7ee40c11195174c5122f324/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4facd65742a024a4a366328a1d2292062d72d6e023c1b7dda8d4c37544933a25", size = 49955690, upload-time = "2026-08-10T12:37:46.644Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/1ecb936ac6409e90a34d58eea1c7cec09a9ae6d2141b9e49ad01a2b1ea47/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa0559502e1cd6254d6814614085dd9c5a3dd0419362978a936a3f68a9e5c3df", size = 53128198, upload-time = "2026-08-10T12:37:52.531Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/5236033550633c9b7377b2a53660b2bbb06cb06dc09c4356332d67643ca1/pyarrow-25.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:62cd0d785b8aa6675ee355f9fc02252a340f4441257c42674937826fd7594325", size = 27857263, upload-time = "2026-08-10T12:37:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559, upload-time = "2026-08-10T12:38:02.567Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383, upload-time = "2026-08-10T12:38:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190, upload-time = "2026-08-10T12:38:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437, upload-time = "2026-08-10T12:38:22.487Z" }, + { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424, upload-time = "2026-08-10T12:38:28.755Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206, upload-time = "2026-08-10T12:38:34.862Z" }, + { url = "https://files.pythonhosted.org/packages/65/ee/197cbf47e49f83e6ebeb946a5259a48a638dea27ac774db42fe78022179d/pyarrow-25.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8858d7bfc22e3f51529aeaa4077225029724623e4595dc9eff8c793935c34140", size = 27953934, upload-time = "2026-08-10T12:38:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8d/8f271a7a034c834910ec925d56fa4b29733b1380f5289419f5aaa3b02777/pyarrow-25.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c7c534ec03c358a76ea3e505e74c1b6aef290af90c444dfd092dbfe23e755b85", size = 35855328, upload-time = "2026-08-10T12:38:45.489Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/5bac242f4e841b9971d5eb94fdfe2577e2b70be983e27401e72055786037/pyarrow-25.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dda9470024204d7bbf2042b47c6e8a0e47a3eeb8e34405882dfaea6577e0c153", size = 37622415, upload-time = "2026-08-10T12:38:51.107Z" }, + { url = "https://files.pythonhosted.org/packages/63/1f/96d03b4e1506524f7087adb0fd6b2f69f0c9c7aaff1ec36d8030082e15a5/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:44a9120ce5bd81936b8ab9a88076e3fd47c2c6838e0e43630fed83626aca81d9", size = 46813813, upload-time = "2026-08-10T12:38:57.773Z" }, + { url = "https://files.pythonhosted.org/packages/98/d6/33a411115b61dbfc16ad6ad73e71730f6fea654ee3667673bc53ab0e2fe7/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f", size = 50104452, upload-time = "2026-08-10T12:39:04.579Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/b1b97c9ca87f9f9ddbb5230c798df94eccce61bd79b9b45458c69a478588/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f89685964f46e4216103c75483aac0c0692a5f72212d7ca835adba5ede56ce3", size = 49951343, upload-time = "2026-08-10T12:39:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/a112df5cfd5a68cb1d9fc31cfe38c28d5aec9f10865ce37ecef2e4450873/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6943e2fe7954d29d84de45d29d34c8dc36ce96570e67d89aa9976e650a4a9138", size = 53144784, upload-time = "2026-08-10T12:39:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/24/97e8bd98f1e3b07e2ba08bcdff690674fbe16d69a7d2712cc3884665e615/pyarrow-25.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:31e49a7888fcdf3a835da33ae777f6bb9a866334e5a789282fc26dcf426f7f15", size = 27870159, upload-time = "2026-08-10T12:39:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/36/4c/b525824ad3094076919273cd97db61fb3d78252dee76fa3b8dc8f76774aa/pyarrow-25.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bf0b672390cdcb640d7288f96b826d71ff4e9abb254a86c89890baf51a29cee6", size = 35885255, upload-time = "2026-08-10T12:39:32.366Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/448bb0e940de41aec31d1a956e63ad9c54afdf122a103cc3ab20c2a3ce33/pyarrow-25.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:38a9a4b4b9613380e200641891495a56c3d5a98a092db4a870af9975e220471d", size = 37644461, upload-time = "2026-08-10T12:39:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9a/13587e38bd4806fd218f50fd13b8903fab60588a699ff0c406372e5b4043/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b", size = 46877146, upload-time = "2026-08-10T12:39:43.722Z" }, + { url = "https://files.pythonhosted.org/packages/8d/61/1c5d1229fa21da4cff5365e41e57177aaac57c563c727f35419b8513d1c1/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:9171748cdf796972d85a4b60157c279913e242992e350c90c7450182a9838b2a", size = 50131616, upload-time = "2026-08-10T12:39:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/43/20/291e1d65cc0b09aa19f03cf25cf51a2f5fa94b5db315178f2d254ed5cad4/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7a296aac7a71fa0886c08e155ddb6c636a50013f801f6178daafa0f9e726188", size = 50008879, upload-time = "2026-08-10T12:39:56.891Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7c/1b7c9ec28e76576337e4f97b31141c9a181b89b6d1d6221e9d8205621a58/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0fe7c8b6c03969b49c8c66182e4a18e3819ab92d07cfab5d8370c531b9369ef0", size = 53170864, upload-time = "2026-08-10T12:40:04.918Z" }, + { url = "https://files.pythonhosted.org/packages/b7/75/f3d789dc06011a765d14d86bda799cf72ac1d715b6a6edecaa0d73d95062/pyarrow-25.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f", size = 28620729, upload-time = "2026-08-10T12:40:51.41Z" }, + { url = "https://files.pythonhosted.org/packages/fc/05/647a8ee6f7c2662feb6921315617bc04dcd6034763fb61b1199720bf6162/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:59a2de54c0cbd954da861eee4d1d330f8e909c45b53455baef696380f2c55033", size = 36130288, upload-time = "2026-08-10T12:40:11.014Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/c9ee997554d7bea94520667dd1933f109ac1da3ee3556d2b49381e023484/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:35935cd5de130aa5cf4dea052a63e6bf2e17006c35c3a468194242b9b2bf5956", size = 37762187, upload-time = "2026-08-10T12:40:16.592Z" }, + { url = "https://files.pythonhosted.org/packages/a2/08/a28c01c7fe9e96e8233ce2d13df1d402f4f999f848f51d2daacd6bb4c036/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44", size = 46888003, upload-time = "2026-08-10T12:40:23.242Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b9/58612e977d28dc58c878448866838369ee8da2f1e7cc8ed2c84b952aafee/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a1fdfc6659b6b19022f2e50627fb5cf7156a66c46bf4299379955cbe742382a", size = 50079036, upload-time = "2026-08-10T12:40:29.169Z" }, + { url = "https://files.pythonhosted.org/packages/72/13/66e1402dcc860e1dc2760b1e0292c9a569b62b3bccab69def1b3e907d006/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:169d3429d5be7c752125890620f75a60776d38b0035eddae939651640822332e", size = 50040226, upload-time = "2026-08-10T12:40:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/78/10/3f1a5497a7ef732ab0f03ecca3e66d89d9c0f57fdc61b4794c456b781f01/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:119297a6dc197e45d9c6d4415f7814a67ffa36c180d26f68c154c58067ae782d", size = 53149035, upload-time = "2026-08-10T12:40:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/93/c0/37d4a7e8e2f7a6076283673d5298018ca26478b934c6ee369e10505ab32c/pyarrow-25.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4288f27577352d608ca08553b0865e4a9b3aa14820c5d95b53337218d609835b", size = 28753071, upload-time = "2026-08-10T12:40:46.623Z" }, +] + +[[package]] +name = "pydeck" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/c9/f71032fca47ecc09d30904d9a610234b07139f89eabc2f054b141edcc30f/pydeck-0.9.3.tar.gz", hash = "sha256:695775cbfe51f5fdffbd9735ba469987fdc5efc96bc40a0ee4808170509c78b2", size = 5900912, upload-time = "2026-07-02T23:27:08.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/34/3998411437aff304a9ed4fa37a6fe1ef3132bcd2b5eac59851b80c86123c/pydeck-0.9.3-py2.py3-none-any.whl", hash = "sha256:d8a47c11c81fb12d51b1feb42427ff4f0e13cb599e48931021b2cba98b6849a6", size = 11428091, upload-time = "2026-07-02T23:27:06.399Z" }, +] + +[[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-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/68/3bd46b8a5e01d3c2ebdf9c5e9497912e3fe0cde02bac21a7130ca866e403/rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12", size = 63948, upload-time = "2026-10-04T16:32:36.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/6d/ddb8dfcf41892ba9c672d27988592c038d30db61708230313831b6780a1b/rpds_py-2026.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2", size = 344947, upload-time = "2026-10-04T16:28:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b2/632c3d034961149eff606e3e357547620a9ab37b849ae5815f67838753fd/rpds_py-2026.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044", size = 346770, upload-time = "2026-10-04T16:28:56.884Z" }, + { url = "https://files.pythonhosted.org/packages/6d/45/4df346410bb873d03ec60c3b891d1fceb8cabde1971d29844bbb625cc296/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa", size = 377795, upload-time = "2026-10-04T16:28:58.193Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b8/a70f302207e4f29f873c01a986fcac5def49b9c8224328cef1ad7da6b7a9/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52", size = 383137, upload-time = "2026-10-04T16:28:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/13/e3/3aa28a48a01169d0e20728bf0e5ebbea3ae2774285b40853ae49b3ef332a/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f", size = 496789, upload-time = "2026-10-04T16:29:01.011Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a7/414e8d088602d66ad6f47c65bf07fe5090992a8574cdd4571c1a83b647ed/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7", size = 395929, upload-time = "2026-10-04T16:29:02.465Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5c/aac3b960fd55d37b49f77c0eebbe043eaf36762ebd48e7b46951e59c1c05/rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff", size = 375426, upload-time = "2026-10-04T16:29:03.944Z" }, + { url = "https://files.pythonhosted.org/packages/d3/80/77987a41c40be7202a78d9aed4a650bfb05792237cfd3b9e22638d990fe5/rpds_py-2026.9.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e", size = 381497, upload-time = "2026-10-04T16:29:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/640b02eb1ecd71a913c1178b6235287b31006266bca9a26862e80e497b54/rpds_py-2026.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888", size = 405740, upload-time = "2026-10-04T16:29:06.578Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8c/d034195d45d215625a23f2ae85dbbdb86a49a93a51272061c97c80b9d316/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834", size = 555850, upload-time = "2026-10-04T16:29:08.115Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/532f1addfc86207bc3cf23749638bb0d457bce891595012ed8a08d0543b8/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391", size = 618661, upload-time = "2026-10-04T16:29:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/50/5b/b0f2c9833a36a5b1f6ac5cb0e635fabacf713266f46ceefba2102da0bf51/rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a", size = 583530, upload-time = "2026-10-04T16:29:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/464ed4b98d3c8c6a334f3b5e24d3886de04b05658a95fc40a37e0e6e4775/rpds_py-2026.9.1-cp311-cp311-win32.whl", hash = "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1", size = 208125, upload-time = "2026-10-04T16:29:12.408Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/384a9b62f8cd89c8e3e609b8c9052c140e8c01d807261e0517c904b8bf29/rpds_py-2026.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f", size = 226045, upload-time = "2026-10-04T16:29:13.751Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/0a691f0dcb2301fa9c736346da2d0d4991778373283301c5c12b8f952f3d/rpds_py-2026.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12", size = 222636, upload-time = "2026-10-04T16:29:14.989Z" }, + { url = "https://files.pythonhosted.org/packages/5d/34/a828586ea3329fbb50895b50e9cf98ca3d924a9f41a0b26d443bff1b3794/rpds_py-2026.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af", size = 346822, upload-time = "2026-10-04T16:29:16.258Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/ac6a0d064982251856ce009c9e1dd51a34110b3c055aa7d1aad18b4899a3/rpds_py-2026.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b", size = 341380, upload-time = "2026-10-04T16:29:17.706Z" }, + { url = "https://files.pythonhosted.org/packages/42/ff/bf7d54f362748fd6a49277b9d6531c394074110fedf50ad791ec59133fbb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a", size = 373053, upload-time = "2026-10-04T16:29:19.063Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/74b0ccdbbd28687b9b5fdb34c1cabed88352182facaced9d6f5486b8b9ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07", size = 377742, upload-time = "2026-10-04T16:29:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6d/b979775a3057b2a5c26d75ecbff60a20a24ef081db6dd76836fca3f20247/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69", size = 487289, upload-time = "2026-10-04T16:29:21.958Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5d/7c34734ce3ece943d9d6ee0122a74a21bbc75b47acb5b7053c83f6efb1ed/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d", size = 390710, upload-time = "2026-10-04T16:29:23.378Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6d/b26eb1e75395925b3a142ed351cbed2ec8212f5c9d937aee3df32c701baa/rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab", size = 371261, upload-time = "2026-10-04T16:29:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/b2fdfe10301a9e27af21e634337fbba7baac0bc17fe44619a17c26bba2cb/rpds_py-2026.9.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e", size = 376075, upload-time = "2026-10-04T16:29:26.748Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/c4b8d954e49c3c69cf8063c0c9e0919c99f3d2cecc46311606a1cbe835dc/rpds_py-2026.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237", size = 399358, upload-time = "2026-10-04T16:29:28.282Z" }, + { url = "https://files.pythonhosted.org/packages/b3/59/9559a7293c97dff0cbb293efffbd36644103883ecc27741af8ef90c84ca8/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3", size = 550267, upload-time = "2026-10-04T16:29:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/6dc60e49511de4c8b00e74f9c1d43aed4d27f712cdb1ade92f2c199cbf64/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc", size = 614018, upload-time = "2026-10-04T16:29:31.54Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bc/92a4ecf27301b886232a413360f6b348a81d55ffef654f2ebda5970ebc3d/rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6", size = 578509, upload-time = "2026-10-04T16:29:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/08/53/efb97f2e6589b7ab8394385a73870e0b2ab2a195281c117b21fd6d7d4855/rpds_py-2026.9.1-cp312-cp312-win32.whl", hash = "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3", size = 205401, upload-time = "2026-10-04T16:29:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/1c/84/1700cc748d0eaa747486e4e82882d2575224e6e9a3148df583058850c629/rpds_py-2026.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f", size = 222923, upload-time = "2026-10-04T16:29:35.709Z" }, + { url = "https://files.pythonhosted.org/packages/c3/89/0302215373c2f8b4408b0fdfee955368cc378a98ff59508d47a7e0dc2dbf/rpds_py-2026.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02", size = 218415, upload-time = "2026-10-04T16:29:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/ee88fd9e756ff93fb6b1182a47ec09504a242620e33ce1d20679efefe841/rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9", size = 346229, upload-time = "2026-10-04T16:29:38.82Z" }, + { url = "https://files.pythonhosted.org/packages/57/71/a097d6552f837500fc36e6b23d09cfb9890c3cc47531f9ca64e149799615/rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858", size = 340514, upload-time = "2026-10-04T16:29:40.405Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b7/497e85768bf4e0d8ddbaa096a4cac31d1509251dee2728a8490aa367e0b5/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063", size = 372611, upload-time = "2026-10-04T16:29:41.778Z" }, + { url = "https://files.pythonhosted.org/packages/52/4b/74ab4108916250b6e198e0d3af05bc6835eb046315f22f7a0ceb49667c5a/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025", size = 377712, upload-time = "2026-10-04T16:29:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/0c/8e/067e77d9d7b3cc793c9d909b7e97e7aadbbb1fb094093b6876902cc96d38/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5", size = 488106, upload-time = "2026-10-04T16:29:44.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b4/c5aae6c2dde269bf955f6b7d35065c655a57e47750d9668052ad67e74dda/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0", size = 390795, upload-time = "2026-10-04T16:29:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/a0/36/76fab39973ee11e7f9f357c55138197bb01c86f6502cb76487e3b4f42db0/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b", size = 370794, upload-time = "2026-10-04T16:29:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/cd2a80e6d7b871937a60e935c5d507aa390d143f4ff3636f640b9733d5df/rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33", size = 375673, upload-time = "2026-10-04T16:29:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/7464a9953724e55a3b3206062fa0ffdeaa519584c6aabf65d3956d94f131/rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83", size = 398758, upload-time = "2026-10-04T16:29:50.601Z" }, + { url = "https://files.pythonhosted.org/packages/c0/86/1534b436700fd49ff411063b7c4d7e938adfabf90895b6cf1622d5a7d1f6/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c", size = 550370, upload-time = "2026-10-04T16:29:52.002Z" }, + { url = "https://files.pythonhosted.org/packages/57/1c/e1fa82a8a01e3c5820f3ba98a8b2673f642128eb368fa88871b01dd2c909/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36", size = 613106, upload-time = "2026-10-04T16:29:53.655Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/b20b8c4c3dde8755bfcd5b08492a02d0cd2e26929aedf6199ca2a377d42a/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d", size = 578235, upload-time = "2026-10-04T16:29:55.157Z" }, + { url = "https://files.pythonhosted.org/packages/55/42/df3f7bbc3f7ab37a8a9db8d6c2ff2c985422899f1f7926afbf7ec3c0b8b4/rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c", size = 205293, upload-time = "2026-10-04T16:29:56.669Z" }, + { url = "https://files.pythonhosted.org/packages/31/9c/ba5a9569d719bfdd6ce863df4133ac6a1658cf1b07cc3534c31db729fbbc/rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9", size = 222409, upload-time = "2026-10-04T16:29:58.049Z" }, + { url = "https://files.pythonhosted.org/packages/35/72/f28ca566f6c23c35bbf7445f65eb0364577b25a026305995e24f78b83d94/rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f", size = 217681, upload-time = "2026-10-04T16:29:59.449Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f2/67b94be1532767803415c1c5a1fd88ea487643d74a673cec1ba140af77bb/rpds_py-2026.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b", size = 347178, upload-time = "2026-10-04T16:30:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/04/37/b751de2b59b0197a1d92a5dd491de88e8a5e928c2e6562581974f1e85263/rpds_py-2026.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35", size = 341878, upload-time = "2026-10-04T16:30:02.564Z" }, + { url = "https://files.pythonhosted.org/packages/72/e2/5873bc4643c250db9e05d48dc0c93763d4aa68bc3b81164cb1af3b45b284/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0", size = 373963, upload-time = "2026-10-04T16:30:04.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/03/5acf7632158247f3f6386ff0af3a1ee48167d575037e8d0920594b76d92b/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477", size = 377975, upload-time = "2026-10-04T16:30:05.555Z" }, + { url = "https://files.pythonhosted.org/packages/e6/00/63fda451b8bffa5808fc8bb311ee7c073b340974b09f273c7b2a145d3d62/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4", size = 490347, upload-time = "2026-10-04T16:30:07.156Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/c093ffd070ba0fb02f76c565d06fecc65ad6e4afdbae78f7031076d3cdac/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef", size = 392962, upload-time = "2026-10-04T16:30:08.77Z" }, + { url = "https://files.pythonhosted.org/packages/22/9d/d08a1128ab199b2f0cf25bfeb0639bd05119fff4b7c47bec24ef9a8ec23f/rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd", size = 371623, upload-time = "2026-10-04T16:30:10.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/c7/4758ddcbb75609414bbccfcb11d612436f9b3ee821bd2f33f0f1604ee648/rpds_py-2026.9.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35", size = 376997, upload-time = "2026-10-04T16:30:12.124Z" }, + { url = "https://files.pythonhosted.org/packages/87/e4/947bd7f608ff60faf46dc9d389c3dffd0e3d767d78a0be19978448ef0ce7/rpds_py-2026.9.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86", size = 400169, upload-time = "2026-10-04T16:30:13.804Z" }, + { url = "https://files.pythonhosted.org/packages/4b/35/fe93e020a0543b5670472c18d7e6af3197c08da571240ce1965c84f85c0f/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e", size = 551615, upload-time = "2026-10-04T16:30:15.332Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4f/5d2a0136bb03b2a56a39dc6ff92d58a6e3e53a2e17079238b86228882f16/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785", size = 614655, upload-time = "2026-10-04T16:30:16.92Z" }, + { url = "https://files.pythonhosted.org/packages/09/1c/3f1025aaf70d9bf7272cc41f8b64ee76b48bf01726248138430e16f23b38/rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118", size = 579602, upload-time = "2026-10-04T16:30:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/53/0d/5c72e6204f76610608706da32b6b7e11ef7e10317558a7bb15a122e008dc/rpds_py-2026.9.1-cp314-cp314-win32.whl", hash = "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc", size = 206315, upload-time = "2026-10-04T16:30:20.05Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0b/489d48abbcc7d70cf3fbf662d9d22abf1f4650761c0a9ae05260800800d3/rpds_py-2026.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c", size = 222852, upload-time = "2026-10-04T16:30:21.604Z" }, + { url = "https://files.pythonhosted.org/packages/91/16/bbb05a7e6a10cf79ba639be7f799d770ee15f64175cc61d081b218dd402a/rpds_py-2026.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31", size = 218709, upload-time = "2026-10-04T16:30:23.036Z" }, + { url = "https://files.pythonhosted.org/packages/22/ac/ac507a0a4ec478ca470440a09583db4be5259ba7670aeba0620822f1e57a/rpds_py-2026.9.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e", size = 349596, upload-time = "2026-10-04T16:30:24.558Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f2/817a46b658d5070f477f722c298ee9a24525b0e4017347964146ef5fdd0e/rpds_py-2026.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96", size = 343114, upload-time = "2026-10-04T16:30:26.048Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/6ade976b13ac1b4cb3bf2eb603f1be2fe74df19a29988d4c2b386be59d6f/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445", size = 374197, upload-time = "2026-10-04T16:30:27.699Z" }, + { url = "https://files.pythonhosted.org/packages/d9/70/77cdf1d3f1a07faabe936016ae623aec7981f73108a8fe7a203ed2e21998/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69", size = 378025, upload-time = "2026-10-04T16:30:29.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/18a44a3beaa9b7931acb04af7bd9539836477c630a794452d4826d6185d4/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495", size = 491969, upload-time = "2026-10-04T16:30:30.995Z" }, + { url = "https://files.pythonhosted.org/packages/73/27/fb39cfd6bddaf741b024f813890374578ff8ac1f1adc473659c048b03b05/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c", size = 393905, upload-time = "2026-10-04T16:30:32.628Z" }, + { url = "https://files.pythonhosted.org/packages/ed/71/0fa7bb77b57af0d710273964180d11b503f62b8a5c358eb2d8c3f62feff6/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e", size = 374569, upload-time = "2026-10-04T16:30:34.257Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/f41cfac269af3b449513ef1bc3d7f32fde52abbbd2d01c7e76b47743acd9/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346", size = 377482, upload-time = "2026-10-04T16:30:35.997Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/312b49006e7f8f9ca5f96647577b8aa6f3df30519c46bd448f5c425af0b2/rpds_py-2026.9.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299", size = 400040, upload-time = "2026-10-04T16:30:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a6/18cca7a878dc7fa95165a83343fd4d7b65643fd22e54e47340a451121d5c/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531", size = 551849, upload-time = "2026-10-04T16:30:39.443Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/1f5685e20f39604691bdc3c05aaa6b8bd2f954e9e996477adf2376768e33/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb", size = 614842, upload-time = "2026-10-04T16:30:41.231Z" }, + { url = "https://files.pythonhosted.org/packages/c6/25/98652109fd9f7e10268dd4571aa52b81987001b806f37ef1a18260de714a/rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa", size = 582606, upload-time = "2026-10-04T16:30:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/19/03/11ca09099bab5f53373a80a334c424ec917c13d050760a59499d2af5171e/rpds_py-2026.9.1-cp314-cp314t-win32.whl", hash = "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569", size = 203341, upload-time = "2026-10-04T16:30:44.954Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8a/88909e3ffd9f47f5b58211473875d8c3c09079f0058c46fb72c55a702a26/rpds_py-2026.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e", size = 222843, upload-time = "2026-10-04T16:30:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/a662f367d4896dd0a10cef2fc91f10b7f08af1c10858e287e019563f338d/rpds_py-2026.9.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9", size = 347463, upload-time = "2026-10-04T16:30:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3f/ad45d03df4f84ebae5439577ee81f3999d182711e82235c8037b6528890e/rpds_py-2026.9.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25", size = 342051, upload-time = "2026-10-04T16:30:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/3dcd68c13d4bcc59c1f7eb33ac8e80698f06f3eb0d8a1e06419836071c20/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871", size = 374143, upload-time = "2026-10-04T16:30:51.508Z" }, + { url = "https://files.pythonhosted.org/packages/cf/0d/68c1f058a250fbd1380ebda9fc227cbf50117adf8ffe8161ef383ea79f68/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91", size = 378604, upload-time = "2026-10-04T16:30:53.206Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d6/2d4c59b85397cb4800594fadf692688ccf5ce556adc930e7a5bf21061a5e/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f", size = 490790, upload-time = "2026-10-04T16:30:54.925Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/7e05dc3aebaf52f4e026766bd668fdd09a9d0e23f64a14686b36b3501892/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d", size = 393146, upload-time = "2026-10-04T16:30:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/e2e9a0a46a74ed51a0498ba1fe10f4ea6b2d9a155f372a2f91e51f18cf10/rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945", size = 372022, upload-time = "2026-10-04T16:30:58.295Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cb/8f8774df5134e23424372838bcc5c7ed4127d723e1ff52f7bebd4dcb2563/rpds_py-2026.9.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297", size = 377092, upload-time = "2026-10-04T16:30:59.984Z" }, + { url = "https://files.pythonhosted.org/packages/90/02/8d7095d73bf9114219be40230baa5df00611e0a82ed9517779ff9c19f82b/rpds_py-2026.9.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0", size = 400830, upload-time = "2026-10-04T16:31:01.721Z" }, + { url = "https://files.pythonhosted.org/packages/ec/02/8206856f8f363cd042a8315dc86b3912f5dcb6d3c66bbb24b69c2bfb0775/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9", size = 551478, upload-time = "2026-10-04T16:31:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/36211f1bb1f0b0313f496d92f5905b74ea107f27cb16fa3355a82f04575e/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6", size = 615173, upload-time = "2026-10-04T16:31:05.281Z" }, + { url = "https://files.pythonhosted.org/packages/35/77/cda0c4a6f055446b692f0ed5692f73707cafd3dff82672ede31b1a9b59de/rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7", size = 579653, upload-time = "2026-10-04T16:31:07.065Z" }, + { url = "https://files.pythonhosted.org/packages/74/ec/d8385f446240aed643b9e92a5055cff3015cc04a13c61f73b2883c478ed5/rpds_py-2026.9.1-cp315-cp315-win32.whl", hash = "sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429", size = 206498, upload-time = "2026-10-04T16:31:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a5/71b5cd00e0521e3b6b81828baea368c62b6b700ebdd9554cd7d41ddf12fa/rpds_py-2026.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15", size = 223121, upload-time = "2026-10-04T16:31:10.84Z" }, + { url = "https://files.pythonhosted.org/packages/33/58/dba857c3bc8221b31b62eb170a3080f4f191de79f047200389b7ed1b06a7/rpds_py-2026.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009", size = 218741, upload-time = "2026-10-04T16:31:12.592Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d0/320ab28ccc1415eeb509d68682b0014fb74690cd49f1c2d29a232475af50/rpds_py-2026.9.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0", size = 349719, upload-time = "2026-10-04T16:31:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/56/88/f5b12f1358f443c08b7ce3cc8391d82d335f4872580e2e097847fd36087a/rpds_py-2026.9.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a", size = 343267, upload-time = "2026-10-04T16:31:16.827Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0c/c765b0059d532acb3b9c45d781ccc22f15a96dbe443d00903f643ba9df10/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d", size = 374555, upload-time = "2026-10-04T16:31:18.931Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8a/cafddfda77564a10cd21184640c3bffda6a2b8d20972fb5dedd5e0166328/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db", size = 378346, upload-time = "2026-10-04T16:31:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/b9/01/5e626016eff72c183bf6c96539240cace15d402468647a453ec08415b2fc/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250", size = 492373, upload-time = "2026-10-04T16:31:22.614Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9c/15a2469e9389242f46896b3f0a01d68caea8a5a35c011fcb05ef333aae73/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f", size = 394371, upload-time = "2026-10-04T16:31:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/63/f5/c100ff77e1e6366e947c75969c258fdfc4b7bc5bbfe7351e62ffbf2a1228/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018", size = 375132, upload-time = "2026-10-04T16:31:26.588Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/fe0269c9de253e99c81cabc12b8971a5feaa083debdaff1221e06264d9e3/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d", size = 377642, upload-time = "2026-10-04T16:31:28.677Z" }, + { url = "https://files.pythonhosted.org/packages/05/65/b34a7b257baccff8f4a24a722933166d4941d5ebdc9c3f4bc4ffcd5ce4f4/rpds_py-2026.9.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b", size = 400855, upload-time = "2026-10-04T16:31:30.802Z" }, + { url = "https://files.pythonhosted.org/packages/98/32/844e54176b6071b90b38a564e6940bc6eb8f97b2890dc709c19db9dec0f4/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6", size = 552573, upload-time = "2026-10-04T16:31:32.709Z" }, + { url = "https://files.pythonhosted.org/packages/17/73/6041d20729dffbfdf155c02d65be58bc225a1c1fb548fd87c23ef306138f/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad", size = 615840, upload-time = "2026-10-04T16:31:34.565Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/418094adaee6b056ce199051b255448ed872829051e341b2294c80da0977/rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903", size = 582764, upload-time = "2026-10-04T16:31:36.665Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9b/1698ebf6b840ddfe8b472198abbed6ace35c6e398faeecd6c47dae742a4e/rpds_py-2026.9.1-cp315-cp315t-win32.whl", hash = "sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b", size = 203795, upload-time = "2026-10-04T16:31:38.53Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e2/91f70d804c61f8eac39a417e82aa1024f655ab9e8bdd7393b196242bc41b/rpds_py-2026.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567", size = 223037, upload-time = "2026-10-04T16:31:40.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/f0eba548e297987964ef8088403db875b333963bf8189f3f563b6936b20e/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30", size = 384112, upload-time = "2026-10-04T16:31:42.643Z" }, + { url = "https://files.pythonhosted.org/packages/33/89/891fa4d81f1eae7fe262ecd0aba2c51de94a116c7f3ccfb034fa75f4c537/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050", size = 389662, upload-time = "2026-10-04T16:31:44.642Z" }, + { url = "https://files.pythonhosted.org/packages/4f/46/912cf623b2263f9c9c2d04e8508fc38dbe89983faeba5fb36ca4558ae550/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb", size = 504637, upload-time = "2026-10-04T16:31:46.667Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5c/7f3cfa7c5f01765c9f47c8398210ed0c1eb686c0e99c223cb1c6776d3fb9/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df", size = 402607, upload-time = "2026-10-04T16:31:49.068Z" }, + { url = "https://files.pythonhosted.org/packages/f0/76/2e915d4115ee453c33e6af17bc1df7e22ddc52974e178eeeb92a78c09983/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a", size = 382762, upload-time = "2026-10-04T16:31:51.24Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/844382925d7b84a4074ad79fc2b9d05eb6231ea243b34f247e68e519b606/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608", size = 389563, upload-time = "2026-10-04T16:31:53.549Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/d6e3951602dc2e24790ef9d299aa24bf5e2493df5043f2c6da00480cc447/rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9", size = 416842, upload-time = "2026-10-04T16:31:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1c/8d248e4ea671d525ab7daa0703ad81cc2175076fa0ccd7ef088facbd2b01/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c", size = 562434, upload-time = "2026-10-04T16:31:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/ad/38/e7ac0db898120d15dea25e3661b1242a40cb3b23d1f4db86c15a01fe0ba1/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f", size = 627909, upload-time = "2026-10-04T16:31:59.977Z" }, + { url = "https://files.pythonhosted.org/packages/03/c8/37e555514932cb1ee36c75349187aa882a98dee5d5654c761699ed8a0d27/rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e", size = 593658, upload-time = "2026-10-04T16:32:02.14Z" }, + { url = "https://files.pythonhosted.org/packages/12/5c/5f69a91add94a8a33acea68a2b034a464946a654ff7296af9d957ef0d6a5/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32", size = 351769, upload-time = "2026-10-04T16:32:04.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/22/35ea8bf3b2682b90ab687c74e4828418ff12298a5b0b0a0c033f61c86a30/rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31", size = 351719, upload-time = "2026-10-04T16:32:06.834Z" }, + { url = "https://files.pythonhosted.org/packages/a3/90/a9ba81408369d34c1aca73319c15adc91ed45c1d21e9e8ee4832ffb7fe0b/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4", size = 381109, upload-time = "2026-10-04T16:32:08.994Z" }, + { url = "https://files.pythonhosted.org/packages/b3/44/5192a0bed94cec86bd2a5e1cc5a1bb8f7eec3aec907eacd2deb13e87e326/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759", size = 388222, upload-time = "2026-10-04T16:32:11.321Z" }, + { url = "https://files.pythonhosted.org/packages/92/cd/5356549711448f18b52f7a11ffbe90f1228774996fbf3c6cd1b18d22abaf/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb", size = 500384, upload-time = "2026-10-04T16:32:13.478Z" }, + { url = "https://files.pythonhosted.org/packages/7a/88/ddda9d28adfe33119c75b2e733d4ba7e326f41d7e1b1093951771768ca48/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82", size = 401661, upload-time = "2026-10-04T16:32:15.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/c3/bb59ba16a6b4a57995d15b8c04c00f28df3b938c4dcdde48fbe0f73edb0c/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c", size = 380748, upload-time = "2026-10-04T16:32:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b8/0580faa1c5a32ddc160130dd7ae54d2d007272a89a40a6b5db3e50f9f585/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14", size = 385731, upload-time = "2026-10-04T16:32:19.855Z" }, + { url = "https://files.pythonhosted.org/packages/bf/70/f73564642bbe3322c7eeef2c2f258cf040b71c41a430b532e59d04a1b838/rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c", size = 412131, upload-time = "2026-10-04T16:32:22.285Z" }, + { url = "https://files.pythonhosted.org/packages/8c/5d/17fff2e1f8f68721a2afb5cb48f7e442c751bb6b4883157c5abaf618cd08/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68", size = 558193, upload-time = "2026-10-04T16:32:24.4Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d0/869ab6fb08531f97aae4a780b4d61b4190d8aef48fe054ee28bcf7114466/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_i686.whl", hash = "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da", size = 622448, upload-time = "2026-10-04T16:32:26.844Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a5/9672e532fe02cd3b92c78cfd8743abca939d96853006c63835009a1ecb6f/rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448", size = 591126, upload-time = "2026-10-04T16:32:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/80/ef/3a9f8e4279c7920af561deed4cb7ebebed2794a8f608cf404d5eef14a105/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_10_12_x86_64.whl", hash = "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0", size = 357108, upload-time = "2026-10-04T16:32:31.641Z" }, + { url = "https://files.pythonhosted.org/packages/b7/55/4b2fa381a583760aea5c92841e4e928a358e1c6511b129219e9c2826a226/rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37", size = 346877, upload-time = "2026-10-04T16:32:34.061Z" }, +] + +[[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 = "starlette" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/2b/3850dc6bf7ef71b088962eba31dafc6cffd2f96e577ebb0bb316df96da3e/starlette-1.7.0.tar.gz", hash = "sha256:c79f74ea63cff761804fbbfb182f1e0b440c2d07b164d24700c5a1bab5d6ff5d", size = 2736246, upload-time = "2026-09-23T07:30:26.35Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/d6/1ec1b290f9e0fb067899b61e1d37a30c923068bad260b216dbe37a7d2967/starlette-1.7.0-py3-none-any.whl", hash = "sha256:67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e", size = 78980, upload-time = "2026-09-23T07:30:24.567Z" }, +] + +[[package]] +name = "streamlit" +version = "1.65.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altair" }, + { name = "anyio" }, + { name = "click" }, + { name = "httptools" }, + { name = "itsdangerous" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "pyarrow" }, + { name = "pydeck" }, + { name = "python-multipart" }, + { name = "requests" }, + { name = "starlette" }, + { name = "toml" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, + { name = "watchdog", marker = "sys_platform != 'darwin'" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/61/75c550a2d2acd79402aa1f32c8068c6cf47fa621d3995883a43caafeeadc/streamlit-1.65.0.tar.gz", hash = "sha256:42acd9ebdf3576a35584977c48a044ec0b5d3e4997fa9248809b9891598ac6a0", size = 9749831, upload-time = "2026-10-02T21:40:36.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/e3/5c9d2e88563c9974e53ac744b1523ebb1fd8f0ebb1ecc28a5f6f6bb0baea/streamlit-1.65.0-py3-none-any.whl", hash = "sha256:517a7254e223f4986d2b2e0745d02acf8ca64656943e63e422d345ce34a7495b", size = 10313469, upload-time = "2026-10-02T21:40:33.164Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/68/f1b440335057bfce71b6e50a9d09445aa2ecbd08359a337976627b8409e7/tzdata-2026.5.tar.gz", hash = "sha256:8cc73c0a0bfca7dbfa59235d60b2eff82231dee33f53d206db1acd9173cfc0a7", size = 200404, upload-time = "2026-10-03T09:23:14.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/21/1e5995a1c920cce14e4bffae20c665ec10e7ed03ab25e006cd741092b718/tzdata-2026.5-py2.py3-none-any.whl", hash = "sha256:b683bd1b6659ddcd810ff02ad09ba821d4bf1065072805063eb35c49617905ac", size = 347996, upload-time = "2026-10-03T09:23:12.535Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "websockets" +version = "17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/89/3f825ab71c242fffb62ea8fe638741c290f62f8d7aadf8125ff897747af3/websockets-17.2.tar.gz", hash = "sha256:36c2fb94c990cc2545143b12690e2de6c16300f9dbe5b4f33fa300cf57dc8792", size = 188355, upload-time = "2026-10-03T14:56:53.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/f7/8a90cc2abbe4709dff4450824beb07cbf7256566ee043c2ba3faa1d5fb2a/websockets-17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:569ed5db651e420b13279f9333443bb5b84a436cc66b599cbc535697ae4434a0", size = 217725, upload-time = "2026-10-03T14:52:50.797Z" }, + { url = "https://files.pythonhosted.org/packages/7f/85/e418ba2e7e412a5b35c42caf6d4fcc8ecee1a66edc4f2a5f780da775aa77/websockets-17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3892d76754b5f36fb40619f3ef09c68e5c3091f1ab8840964518ae5a41f30952", size = 215415, upload-time = "2026-10-03T14:52:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/e4d7eb2e2e4ffed0b0dfbd2d1aa3c8101f42d34ac9f58b47b822c565d1d4/websockets-17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5436ffea003adb50e283ca0684a3fcaa1396104f841736c3322ee6582bd09e98", size = 215690, upload-time = "2026-10-03T14:52:54.173Z" }, + { url = "https://files.pythonhosted.org/packages/4b/dd/e8718fa6114c4cd15b05133b548af985638e80774253c1faee8d49874c38/websockets-17.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9df9d048def11365d170b375b6ffc8b23a7f188c3560acd4418ba088ca2e2705", size = 224756, upload-time = "2026-10-03T14:52:56.132Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d5161c46f3eee2ae67cdec489532b51695a1c27ccfadd858dcd419ea26ac/websockets-17.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:376a693697ddb695ea282ead76060f4847f90e564b12b4389f2c7589e6fadb9e", size = 225026, upload-time = "2026-10-03T14:52:57.671Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9a/3f83bace9636af07d7bb00cbae0bcb5bd1697892babac79664f3a2b3a011/websockets-17.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd63d0c7ed0d3d719c91b5a3861f0f0b3cec9bf223033ddf69d17aaac74bb6d", size = 226260, upload-time = "2026-10-03T14:52:59.114Z" }, + { url = "https://files.pythonhosted.org/packages/03/50/5347cb13f97430526b9c31e9b30fa639bb1d0f9d53074da8622b327cfb6f/websockets-17.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:48997ed4431d8006988788ef4b62e1fd3f053c7463b4fa793aa6c4f9e96a3bb7", size = 229573, upload-time = "2026-10-03T14:53:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/14/2b/7511082e3fe0cc3233ecb0c3b019ef12c1cd9df60ac1a7858f6093f490b5/websockets-17.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e312e07557a5ad348f4e83d3419773527f6e790c7f97928b1911d767b6ea1c7", size = 226819, upload-time = "2026-10-03T14:53:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/4f/86c1a9db323d4fdbf56cc089942f18328a48c3efbbad0d625a66a2195842/websockets-17.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:902ce8cafca2dc14cef9558a6fc3b45dbf7f121d1404bf2ad18a1c894555e48c", size = 225595, upload-time = "2026-10-03T14:53:03.768Z" }, + { url = "https://files.pythonhosted.org/packages/81/92/4f54f6031d97e284e01a0728cef38b095478dcaab81837aac8cb0e26ea6a/websockets-17.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e53d950e16d4bb672a5ff41fe3131e65a4e5d688d694e1c7074c8c9990bb3ceb", size = 222875, upload-time = "2026-10-03T14:53:05.7Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/c6d59b8b45c730a56ee5acf6c0ce9896356cba25ef3f9a4c9d1796f2e44f/websockets-17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:946ac2164d646e733004946ae39536b5af473853183d81da5962e29d36e3ad35", size = 225745, upload-time = "2026-10-03T14:53:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7c/5d9b91b43aa339b96551630940a847270c10a9d70243be4c81fe5dc6fb34/websockets-17.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:660aa158127035e741d4b1835dbe79ae18a1fbb21ecd236655f31d60110e68d5", size = 224342, upload-time = "2026-10-03T14:53:08.893Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e1/c90c24b0dfb12b8b6f0d5e13fc7cf9f121a2e072f7f54bb888da826b2012/websockets-17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4733fc2d99fe888261417b7e29995403a72d9ffa78629902882325ea141177f2", size = 225109, upload-time = "2026-10-03T14:53:10.495Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5b/f38ca1299c10ea1cfc7f1d129c65a15e4f4b281d1f3dc25891d5fb9bf9db/websockets-17.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c2ec7e51157a3fa0e9cfdb1a8969bab38d1c22ad1ace7c6cea006383b43a1ad4", size = 226151, upload-time = "2026-10-03T14:53:11.976Z" }, + { url = "https://files.pythonhosted.org/packages/f9/21/ff6089c6921c7ae0e1801a4948aa1a3831deb1596e8f0d1cd3a0c0e44109/websockets-17.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ada04d0262ab06527054a2a497f384d102698ff39b3865dc566a7d24b6f4058c", size = 223732, upload-time = "2026-10-03T14:53:13.864Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c4/01ca4212f665e351123c84e7f7156badf5da958ef8aad8781b538682c699/websockets-17.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c393a202df08e96ed619310f0cd78be700e532a57d9a6ceee5f80b4e35bef14", size = 224764, upload-time = "2026-10-03T14:53:15.411Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/bc17b39d1e62b771d8a417b714439252d7abfca21185242cc293d75b20d5/websockets-17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af4c565b923bb5975401b8e4cedc2e17b2fdbf33b905737ee12384e6a6fd9507", size = 225002, upload-time = "2026-10-03T14:53:16.93Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f6/ccab831ab6a841a35134937a1794c0f3f09ccc604625505be061dec5b3e4/websockets-17.2-cp311-cp311-win32.whl", hash = "sha256:c81d6cdbacccda7e0eef3b076a457fd14c3835cdbc5993d2881580c2fb1f5f26", size = 218226, upload-time = "2026-10-03T14:53:18.376Z" }, + { url = "https://files.pythonhosted.org/packages/0a/18/4fcc23f2159393ad7a668574ee97ee5a135003bfcbdd56b30581110c0fe8/websockets-17.2-cp311-cp311-win_amd64.whl", hash = "sha256:55c5b9eab079540bfb639b40b07b7b467e5c5a7ecf97a65cc8665781381c9856", size = 218523, upload-time = "2026-10-03T14:53:19.947Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/5a3f4f75dadb7fbf980ea4b59d02528f87fb2d3c0ac120c2ff50d1dc1b34/websockets-17.2-cp311-cp311-win_arm64.whl", hash = "sha256:55f9a808a0e072473337c240c939849818276e288e2374b832255b5b791b0851", size = 218454, upload-time = "2026-10-03T14:53:21.417Z" }, + { url = "https://files.pythonhosted.org/packages/bc/de/87854af9b38fe4738fd85f7f21c5b49558ae20aec898880894e435f33375/websockets-17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:916ebdfd82e7fc68041d36b2b5f60361b9abce1e087454da15f8bd004839e090", size = 217757, upload-time = "2026-10-03T14:53:23.029Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2e/1e80b5efa41544f626d56bd15ccb53dbfc56bf28bf80ab9cd6f82c4b1d20/websockets-17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3621f3686397708b8eeabfd0a9d75267c1f29a7537d2fe31e65d099e71587fa4", size = 215439, upload-time = "2026-10-03T14:53:24.531Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6e/82c78b595aee05be76a7ee78539323da1593c1848e4fef51c704c696568f/websockets-17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a81e19710d48da88653473b6b9c366d47e99fe4f58e37ce415be47966748f31f", size = 215703, upload-time = "2026-10-03T14:53:26.226Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c4/905ef6aa80423c03dba99e1e26fc0acf63a2a9a6a2d9e8c0e6a63caaf952/websockets-17.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f2731f9067976c8c4127212c0d2f2ada42d497d935e470419e029802365b12bb", size = 225023, upload-time = "2026-10-03T14:53:27.744Z" }, + { url = "https://files.pythonhosted.org/packages/03/c0/a6d8be9c43e4456fb9597fdf8b5e0ce1f0a5df41503acce6d869536e4e23/websockets-17.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6627b913b8586b1c06db9516b31dd0dfbc621de3bb9312616d92a7e44f268a5b", size = 225299, upload-time = "2026-10-03T14:53:29.171Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/976d34b5491258b0a86c2ce9b9aabb9fdd68919ffd7fe65999c14a502a98/websockets-17.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0198c4ec6a3406a2f7557c032967de426474c2c995c81076585e09d29a9f407b", size = 226540, upload-time = "2026-10-03T14:53:31.635Z" }, + { url = "https://files.pythonhosted.org/packages/83/2f/c4cfd42f53c697a8ed123fd82b8f85fcd13b6360d47f9f1d1d45d6ec6627/websockets-17.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:88c6a42c2632ff469e84155e44f6ed92cb15ccb047bf5fcb59225ae5a12fd33d", size = 229371, upload-time = "2026-10-03T14:53:33.061Z" }, + { url = "https://files.pythonhosted.org/packages/e7/55/9a221b29c6232ff9282eecb2fc102402cb9e42a3479264db0e5fc4fe6835/websockets-17.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:eb0023e6cdb4b8ece0b33875188dd16104ad8c335361d396a98394f99e30ff7a", size = 227173, upload-time = "2026-10-03T14:53:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/8f/07/125e6d010c56c253d3d2b93cabaea0f96d33898151a16b49066a594acecf/websockets-17.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c1c09d5d4646eb96bda2cfb97493bcea21a0956a981de116e6b1f4a9de07f3fd", size = 225929, upload-time = "2026-10-03T14:53:36.071Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/aad3bd902aee84e1b261ad6ab83b405e4a564af43101b8ad1dc0293ff4f4/websockets-17.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0360c4dc13ac569cc245e0efa2f4d4b1e4733d24c47b8ab3f3747227b1356348", size = 223167, upload-time = "2026-10-03T14:53:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f4/ec8ab9be1a5310b4fea829f088c7aa2b7a58b61d34bce1b2a9338635ff12/websockets-17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76693a16dead737946b651375ee3109d7db7ad9569a1c55c60aaed3ef85cfcc6", size = 225974, upload-time = "2026-10-03T14:53:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/65/45/ba6503f8257d3f98b0f07ebaad0fd099c9023eae744fd5b775416743597e/websockets-17.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:77a42cc507993ec5471b5283f7eef869239173b6000031543e3938a86d1af0fd", size = 224581, upload-time = "2026-10-03T14:53:40.496Z" }, + { url = "https://files.pythonhosted.org/packages/d0/45/05cca59a876c6776727d96fc7ba59e0b6f9aa496afbf13e7e04ad0b63678/websockets-17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3bbc5543e39ee025d524077c5c15c2d67bc11c9f6676afe5b531839e24d701f6", size = 225347, upload-time = "2026-10-03T14:53:42.061Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/cf0e43292ae949b13f67535be84317102891d69fd1986ec2bf2ead42747b/websockets-17.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8da58558bfb0ca6ccac2419773521f1111e40654038b1afabdfc69c02cb82614", size = 226457, upload-time = "2026-10-03T14:53:43.575Z" }, + { url = "https://files.pythonhosted.org/packages/79/0d/9a5c61a18f0cc9876d94c70ccb3daf7614a9fee56abbb37c0e64e757fb96/websockets-17.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:01420cb1cb47433e8e7075d32cb8017ad3ffed0654bd1e48c0251b865920dec3", size = 224011, upload-time = "2026-10-03T14:53:45.077Z" }, + { url = "https://files.pythonhosted.org/packages/34/ed/991c1ab80ab2ce40e1c939fef6fa8f971c3ef3b21caf988a7a107e0ad27d/websockets-17.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c49c9edd47d0e44d360299e2d8865e2950d2fcf1b4098782c9d7dcd070919e5a", size = 224990, upload-time = "2026-10-03T14:53:46.8Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7a/363c835d17923e967fb66376188e67b9a261c85d826a0cd5e4dd3471221d/websockets-17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96f6c8d0fe21930d1f982bfce2382789d2e8d005d2ab63d21280660f95ef8fe1", size = 225265, upload-time = "2026-10-03T14:53:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/c8/90/6c51f6d78636bd1cd6781fae8ea5ea7bf1d5b4059354f3c1f5f8de793338/websockets-17.2-cp312-cp312-win32.whl", hash = "sha256:b25659ab2d655d742701487d5591e3f98e8f8b329fc999e05e3d59691ab344a1", size = 218228, upload-time = "2026-10-03T14:53:49.867Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/90008411c652dcfae34345a2169f4becd066a4ba71eebfa8dd801e0445e1/websockets-17.2-cp312-cp312-win_amd64.whl", hash = "sha256:faa763b677e96f1beccc6b4d7e8c079dfeed2f249f57a19debc321b519ee64ec", size = 218528, upload-time = "2026-10-03T14:53:51.486Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a1/b8ad6c17f8e75ba2215422fffe0d7f0c4b690dcff1c47c0473db0d253d51/websockets-17.2-cp312-cp312-win_arm64.whl", hash = "sha256:63499fc49efe48bccc2fca40723bc7adb198866cbe159093dd979905316994b6", size = 218457, upload-time = "2026-10-03T14:53:52.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/54/a935a32dbc2e7365b1b59eb74b5ab7515456f02370fdca4c4efc3574e96f/websockets-17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b24b83fbb34b2d8de06cf0f0d4bd7737344ef854482a614826d4356c0c3f0c12", size = 217752, upload-time = "2026-10-03T14:53:54.59Z" }, + { url = "https://files.pythonhosted.org/packages/cd/95/cb8881851abe2662730e6c61cc521b4c96513fdf9103a44f169afce2eba8/websockets-17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8a829db795e3f87053904493d184b185c8eb1f497c852f434168ec856aa6f997", size = 215436, upload-time = "2026-10-03T14:53:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1e/621bb93f35ab7d337be98f1958294437527e2a1797089b5e734ddc5eec5f/websockets-17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf8811d285acc91216368df7fb55cc8c9bf6fcd90eea42429c7186c7385a12b9", size = 215690, upload-time = "2026-10-03T14:53:57.587Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/49d0c983c082676d5d413b28e6ba5ae1d174c00268467bf78d9fe986a2d2/websockets-17.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89c4898da776193577279173dcf9860487590611d7320d379435a145881b048d", size = 225080, upload-time = "2026-10-03T14:53:59.081Z" }, + { url = "https://files.pythonhosted.org/packages/04/13/95a45eb410019772002d8f53d81396dad4120f7df39ca9962f86f5d7cd01/websockets-17.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d87091c4347daadbcc0833b65812ff38d7350c67339625d4e4a512cf38e3e8ef", size = 225361, upload-time = "2026-10-03T14:54:00.61Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/0f0eda80bb441f54becdaf793eb20ee080926f8d2356388377cf262187e5/websockets-17.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1110fbfd530c447380e6e6db88b7e43ffe33d54178f5b0ff0aaa5a280301e668", size = 226602, upload-time = "2026-10-03T14:54:02.098Z" }, + { url = "https://files.pythonhosted.org/packages/5c/36/067fc09d8e6f154abde7c2f747c52cc442a02c5eb14816f5c39cb9f8bcc6/websockets-17.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:83abd8beab056aa77a116364811f8fc262dffbcc7abea48de0c85ccbfc6f1428", size = 228035, upload-time = "2026-10-03T14:54:03.545Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a2/939bade7a396b4c381aebbf3941969f124d0f98d56753f81cd256f3fc4d6/websockets-17.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:876da8ca5520d65b5d0f2ca6b4e7a00d35bb90ccda35cb2ce3cda4b6c711e84a", size = 227227, upload-time = "2026-10-03T14:54:05.045Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8a/37b1033e21709dd7fa39239ea4d9cd7f348ad5bcba94eb47253878576f8a/websockets-17.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8462395df8f224d2daa3d80db3ae4450d9d4b7243c8483ac79a82862f1599dd6", size = 225985, upload-time = "2026-10-03T14:54:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3a/0d89539900b06d86366facb7558198046de125ab8c371d9248d6262da70d/websockets-17.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6e9a04e69456015e6ae5e0d486d995137fd435794442122b00ce5f9526ea3ba8", size = 223226, upload-time = "2026-10-03T14:54:08.583Z" }, + { url = "https://files.pythonhosted.org/packages/31/9a/bfc5633e3d538d0a71cfbe7a5fee56c712e16c2dbd0ce17c83196a2a96a9/websockets-17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a2321bcb73758c44c8076509024d02c15ee484fe77ce04edea4bf4d257492cc", size = 226042, upload-time = "2026-10-03T14:54:10.254Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/cbaf1786d8e3aeafe9d76951fc01139ec353b92555580336f23669382a55/websockets-17.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8be4a87b3baca380ec3c7b1643b2dd268ac9d42c5097c0e8dc9a49342faf4774", size = 224639, upload-time = "2026-10-03T14:54:11.911Z" }, + { url = "https://files.pythonhosted.org/packages/80/49/175faa5bd169486f835602ac0ae6303318aa65693b79cdc72c5ee53b148d/websockets-17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:eb7b737ce8d18c8a08beb68f751572b7bf6a18093ecd1406ca1256b50592552e", size = 225407, upload-time = "2026-10-03T14:54:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d1/3662f612456cfb2dcc128c8e596f0a55fb7b695025e2ebe8ba2abb355c3b/websockets-17.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d6605630c2808b33f362d6d08582e79821f77ed2bd3f49f9d467ea70defea06d", size = 226513, upload-time = "2026-10-03T14:54:15.046Z" }, + { url = "https://files.pythonhosted.org/packages/73/6b/07af5177a49e30156b0922556fa93624a920a2b17d3e63bf4ad94668112c/websockets-17.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:dd9252828073fd0d69e7667af4275a1b17c18d0833b1ab7f59db272f194a6b9a", size = 224072, upload-time = "2026-10-03T14:54:16.574Z" }, + { url = "https://files.pythonhosted.org/packages/eb/34/d18054ff4d8314524164f8b8efec2cb17627287e099f122c28ed6fa598e0/websockets-17.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:06c7386128a9d85de4e1960114604f3031c084d2f4eee8db382637f1634cbab1", size = 225022, upload-time = "2026-10-03T14:54:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/e9/12/75433caa3e9fa3e51d7751dc6bad24a86addf76cbfb51e52b11d037ba7fd/websockets-17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:98f2d03df74977fd252831c997c388cd6c3f691a8a9d022b266d3cbd9849838f", size = 225303, upload-time = "2026-10-03T14:54:19.679Z" }, + { url = "https://files.pythonhosted.org/packages/6f/de/23e21c002aa2786ac9807c0876faa3b2576493b29ca3386287b0db46f021/websockets-17.2-cp313-cp313-win32.whl", hash = "sha256:5b43a1f7e4853ce08c3f6d3bf69799ee5b46548bfb71792a8158f7e45d66b547", size = 218219, upload-time = "2026-10-03T14:54:21.232Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/960411c0c574535d629c16e96a2b4e5353dbe4109df8ecea859e1b5245ee/websockets-17.2-cp313-cp313-win_amd64.whl", hash = "sha256:27c7a59b5352a8f741b422820adfe89dfe47c8f2d84fb32111e76111edaa0e83", size = 218531, upload-time = "2026-10-03T14:54:23.025Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1a/3ac07bb52378952eff1d52d04a7ee6e82ce84e3da319a52a4739cd9c78f5/websockets-17.2-cp313-cp313-win_arm64.whl", hash = "sha256:533b7c82bb1eafbeb921dfe131c9f88e55451ddc328d84bde1c9340ba72d2808", size = 218466, upload-time = "2026-10-03T14:54:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/8b/74/6bc991a28ac983600e65de408ebd1b1413d554ed0468ae5c831bc52dded6/websockets-17.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ecb748910e9ba4624ebe2057791df51dcbffb48c37108ab94a3c593472023c9e", size = 217791, upload-time = "2026-10-03T14:54:26.381Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2f/158e99426be6e71d09520bae53f29294fbb614b2fc5fbf8867b1d08395a7/websockets-17.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2ab9af5cb7265899e659f079eb71691375a1025b6d5fbd3caa495dd08f70833a", size = 215486, upload-time = "2026-10-03T14:54:27.962Z" }, + { url = "https://files.pythonhosted.org/packages/5c/09/1abf942723c0001d9c2fca1551907dade6304517b982b0bf10bba107fa81/websockets-17.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:06e46da092bca3a52e98f0458c66b247993ce501a07cd09c858be3296511ab7d", size = 215699, upload-time = "2026-10-03T14:54:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/1ade03963ef497c47e6bad79e24370827b2fe6145fa8f58070ff2b7dcbac/websockets-17.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fcce735ffd72ac4056db05325d9f0232382b74826f0196eb6a15ca903abdaa0f", size = 225081, upload-time = "2026-10-03T14:54:31.278Z" }, + { url = "https://files.pythonhosted.org/packages/9f/fd/47b8a0361c49da939b976a07b27a72a9f893d01dfcf4d2a28b53419ce1ef/websockets-17.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:42cbca10f82a8b2fb1536e8a0830ca6ceeb6bb3d8d64b766e0795369135654a8", size = 225430, upload-time = "2026-10-03T14:54:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/f0/26/f4d4c76264ee037c5556ab5f50fcba302746dabf7528955534e4dda9965e/websockets-17.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63ff5a21f26bd0e6a8464b53fadbe174825c8718ac14180df45665eaacdb6af", size = 226676, upload-time = "2026-10-03T14:54:34.833Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/c8b1c981322a050c4babfd327ffc9880f9c3834f5b15d2574e37eeb8768c/websockets-17.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63f543463601c1558b755f8dd7618b6ec3dd0934dda051d3b7030d8c76e54de2", size = 228048, upload-time = "2026-10-03T14:54:36.424Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5a/1cb29ddb23e6bc27ffd1c5316cd3616360d1ba0c3854eaa134ee3207bd28/websockets-17.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4c32eb565ad9ce8a6444248e5b7a19dbb86a81c811fe5fcc2fba7a735aed5163", size = 227281, upload-time = "2026-10-03T14:54:38.01Z" }, + { url = "https://files.pythonhosted.org/packages/ba/64/135274572dc0c845fc1111e2b932c807c395daac75d6eae6cfa148d8a208/websockets-17.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d459bbb6c22f26dcebea56924a362aba50d453b9867912862c970434fcf0d94", size = 226025, upload-time = "2026-10-03T14:54:39.613Z" }, + { url = "https://files.pythonhosted.org/packages/58/75/f1e386aec3124489411caf5138cdd5a2bc43d3fd4a681c69adcf5f6272a5/websockets-17.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f19ca1a21871f024e38faf4107b433047df27558dff1b72a1dac31481e2c1fe5", size = 223277, upload-time = "2026-10-03T14:54:41.165Z" }, + { url = "https://files.pythonhosted.org/packages/60/eb/24733a0f568c2eb99e60f9faa620a98fb228c06a01e7e2f348b33290ed9c/websockets-17.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c76b4bcbf0f713194591673fc86a42820e14da6bbd1bb445d3d002cc4d1e4521", size = 226148, upload-time = "2026-10-03T14:54:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/55/6d/ea66a30af74f5983cae31ebb9ef78b178b366a12856a414e1472225c4a34/websockets-17.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:30201a7f69833b015556c72feb69ea501b645986fd0b90dab13f589e995ff428", size = 224615, upload-time = "2026-10-03T14:54:44.41Z" }, + { url = "https://files.pythonhosted.org/packages/87/80/c6f2228ad89774429d270179375ebddb657119215f52d1df7c680d65cad7/websockets-17.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0c8600aec354cc259f1691b0b42816f04a9886a953f82cb227246df76057f97a", size = 225398, upload-time = "2026-10-03T14:54:46.063Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4a/3d8da19732ad468d4be7f1e3ac298078b60bdda55edde6589bef84a5eb7e/websockets-17.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:307fc22ea496be8542d67b82ae8c867a978dfd19ac35573d4f15943fd9277dfe", size = 226571, upload-time = "2026-10-03T14:54:47.672Z" }, + { url = "https://files.pythonhosted.org/packages/58/22/1231657122d9cc24791bb90af13cc2f4e84cf0d3a454cb37e3abfdcb2fd9/websockets-17.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9c88697fa943bd4ef67cc919a17d81de6581846f52bfa8c6f64a916098986556", size = 224125, upload-time = "2026-10-03T14:54:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/1a/04/350ca2445da758bc42cdb4218b44d4ce0d5a9c1d5e4cc4a58d64348ad9da/websockets-17.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f7eac84d4969da82166d5e90d9c38d2f416fe24f9708a7013569b193745b9a31", size = 225081, upload-time = "2026-10-03T14:54:51.075Z" }, + { url = "https://files.pythonhosted.org/packages/da/c4/dec952b0df3a5d918ed2a545abb0c25ae519c3bc2d9aba3b7c46abae8f05/websockets-17.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:313f6703023d53baabab6d6c5c37cf637b2c4fee255acf2ed5e92ad69e28f1b7", size = 225376, upload-time = "2026-10-03T14:54:52.675Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b4/198a260afbcc086ff4979774e51834ed7fb5b95f9ef305e0c4924630b857/websockets-17.2-cp314-cp314-win32.whl", hash = "sha256:08d90cf344bdb971ba3a826b78d4da9bfd56cc6a97a604d9b88cbd40bfa6c735", size = 217760, upload-time = "2026-10-03T14:54:54.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9e/0523f8bc2f7aaddf39562d4fa01b4d38fa61b23d980917a16d2dd19c8dac/websockets-17.2-cp314-cp314-win_amd64.whl", hash = "sha256:dac93bf7a9beb215be3282b8441173cd50806c41c007b8be9bb24e03c60ad563", size = 218104, upload-time = "2026-10-03T14:54:55.845Z" }, + { url = "https://files.pythonhosted.org/packages/55/17/7b8bb4cb64a199e7082f1f9be784d657842fefc327ac777d6c1493504804/websockets-17.2-cp314-cp314-win_arm64.whl", hash = "sha256:2ab742249f953d148a9ba696c8b9944361e8cb92e8bc61ba2dd53a178403afd3", size = 217989, upload-time = "2026-10-03T14:54:57.376Z" }, + { url = "https://files.pythonhosted.org/packages/ee/76/f54ed054b6e860f1e0bbc7019542a048352d41231fdff6d904b379f881c7/websockets-17.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a69ce25be5f1330ee1c74eb6fabbbceaa96b384beedd2627cecded7546490c40", size = 218125, upload-time = "2026-10-03T14:54:58.943Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4c/0f3375cea66a125ae01d21fb9c537aae955ef499bfe7e2b2376a34362f2a/websockets-17.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e24b878cf54843a63985d90480f163ca7f692689fbcbe9cdbd8165521083a8b", size = 215658, upload-time = "2026-10-03T14:55:00.674Z" }, + { url = "https://files.pythonhosted.org/packages/0c/05/7c871a67bfb4b61adc1fe13583db97803f87dfeca644fe6ef51df7bb276d/websockets-17.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f33c7908a6885dcae9f462a4a8347b637053b4ff2b96beb4c23fba1cf7818e5f", size = 215858, upload-time = "2026-10-03T14:55:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/41/8e/59df4d9cd357e902d1c74b13c3c0c3841c8df6e4b1b3d131bf26a23fdcb1/websockets-17.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c796a1bb3e4015249639849f30e8e680df8a431b45d417ba8acf843d2451d95f", size = 225443, upload-time = "2026-10-03T14:55:03.966Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/5e486a3a44e041203c62eccf1fc89c7f8824e21104a7b82b182e5b21c228/websockets-17.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:983bcdc898662f6ba9d6a025c30d29946ff0986d9ad60d400af0da3671f7cbf3", size = 225726, upload-time = "2026-10-03T14:55:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/f0/98/b6eb53121c91fbe8b6897aba06861ce60f9ab58faffc6bca5750cbc21681/websockets-17.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e0f088ddfd9d9bc5019e27ff3767411779e92b59db5bb1507f2731a5b61158", size = 226895, upload-time = "2026-10-03T14:55:07.626Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/8c091321b99c91eb3eaec9acbd940e69308b4e465b5605c430af0cf7d3a5/websockets-17.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:19e2511412ad3393191de652513bc7a0ca3c93af143b32d96d46e59fbbddf1d4", size = 229040, upload-time = "2026-10-03T14:55:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/1a/96/3a92f944305b7de42fcb7530b9fa69607b4b4ce993c36a9f2330dbc318ba/websockets-17.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb5e2bf969ac99a6ae3c71208a5eb05cfde973192540ffa6e1068b57fb78c4f8", size = 227469, upload-time = "2026-10-03T14:55:10.935Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a9/624f6d75ba326c22d03698b34c0ada984f1d76196322a62f6c22903b831d/websockets-17.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:691780fca2be3dec512cb603cb91060271968cb4af86b51d07c57445c5754a37", size = 226202, upload-time = "2026-10-03T14:55:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/47/af/1e6e8c625aeb268830af2c4227fe05e8db59f4f4debe1dadfd0ada214895/websockets-17.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2d39c19b1ba6a6791050383fd69efdd3b63533e2254693d0263879cd5f5921ba", size = 223743, upload-time = "2026-10-03T14:55:14.164Z" }, + { url = "https://files.pythonhosted.org/packages/dd/81/33c5280f4f6f81637c93ae065c6a594dfe35935622af135a5f7c3768bf22/websockets-17.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e48ac2b302986c6f55cf61e8e36b4dd97d0132c5078a713a697a940934ba422e", size = 226492, upload-time = "2026-10-03T14:55:15.796Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/7aa9fc36e67caccbcfee2c48f4ada41e9da512d41523c024d039f0f22ba3/websockets-17.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e136197f1262620ef2e507afc3ea759c1ae7d221886da20eec5f4c9f2618c2aa", size = 224940, upload-time = "2026-10-03T14:55:17.661Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/457aff7081a63d1261608bb4d7b0b0f9dfe780697a2a334671745742850b/websockets-17.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3eb44019a2b0b3b91bac95998f1e4e5589730421170e060fe654a2b7be727dc7", size = 225835, upload-time = "2026-10-03T14:55:19.607Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c3/7a13a3b3050db2c36772ded49f8d48f99eb080948e9f6f762e7529925ab5/websockets-17.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e5855e574804398859c5fbaf4fc7882b96278b7f6572a3d889627e6eb6cfca59", size = 226848, upload-time = "2026-10-03T14:55:21.274Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/d5b2c1e473b1031a4a0ec0e10de69df5b981ab4a10aa482bb45c18dd43f5/websockets-17.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5dc29815520c329f5662f6eb3ebadecf0d4f8c82dfa416d4d6efbf8f39245559", size = 224541, upload-time = "2026-10-03T14:55:22.874Z" }, + { url = "https://files.pythonhosted.org/packages/79/5d/bb81976cc1aa546afb51395ce42913521e9dea062bb34a61308cfff30726/websockets-17.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d1a4f9462da6496b6cb79bbb09c60d17f7e63e8a1df136797b3afabec9560e4d", size = 225315, upload-time = "2026-10-03T14:55:24.443Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/314962d5440c61b4c107914599c13ceeecc6bdb6e2e73a5f7e566a7d1f26/websockets-17.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9496bff5541086478264678bac73c0a75b2fde94fdf6568893bca1f7c6d50d18", size = 225747, upload-time = "2026-10-03T14:55:26.033Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/9eb64b34a3a4458eb08f3f24bde01508f72a00790330723c158ebb965048/websockets-17.2-cp314-cp314t-win32.whl", hash = "sha256:e1e3bc8090a7eae79fdf634b63bdbfa3c93999991023c37c6fd3b469fc8ff5dc", size = 217891, upload-time = "2026-10-03T14:55:27.681Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/3a4e2a09b0822d6e525cbc6e44a4885669bad5b22ab9c64fa2444bc15325/websockets-17.2-cp314-cp314t-win_amd64.whl", hash = "sha256:65a89a5bde227bfe908016f35b5bd347970cd1e5b0360f389502eba1c7fde6e0", size = 218229, upload-time = "2026-10-03T14:55:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/cffb75ee746dd060984c3c3e2eac7f875a866225a30dfa53e2cd18232565/websockets-17.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1c27339934109dfaca83f18ab2c23db06714e9d5deca2c8e37e8f492ab90d20b", size = 218146, upload-time = "2026-10-03T14:55:31.001Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/10a9b1633b63594054c87b97af048628cea2b21b5089a52a9fc1e0af60a3/websockets-17.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:a7c4bb26de6ef496d24822aee4f6a305d97cd33d21a2b85f290292d69ba1c25e", size = 217719, upload-time = "2026-10-03T14:55:32.674Z" }, + { url = "https://files.pythonhosted.org/packages/0c/00/ff4020fe0886dac7199a16ce2805c7afd7b981bd2e81d3fa18dff5d9863a/websockets-17.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:c08da1f15040bd1e1a6074bd4518a6ef20e67b1594ecfb0aa75e5b45f87e6d6d", size = 215448, upload-time = "2026-10-03T14:55:34.338Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/bc7b944f81514378b2c2ab96c17df19e871cd33b9be0f1f6dfc975457e5e/websockets-17.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3117abfd32b183bdb6194df9317766d32c6517f3d1c0aa8c62d5c6ccfda0b4a8", size = 215674, upload-time = "2026-10-03T14:55:35.918Z" }, + { url = "https://files.pythonhosted.org/packages/a8/da/2b2b76faa2f10c4813e3872c9577fd13a798f5918b1785b86ff7d635eb2a/websockets-17.2-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a046227daa7f191e843d26b911c1146233e9a33d249e0c954dcb3ac7c398710e", size = 225119, upload-time = "2026-10-03T14:55:37.777Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d4/22cbe288c0d5cef7620503be92c0098d82220353fc7e188034a19c517240/websockets-17.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2901bdf24f20bc884124b3e88c61f7ece260c20c81e610f2196007395264a4aa", size = 225549, upload-time = "2026-10-03T14:55:39.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0a/504b0d3063679f2c60430c3539482d42a4cb8bd1a76646baf742030a93cc/websockets-17.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60e39adfecf998488166aca8ff24ab1ac406c9ecbecbcf9b3bcfc43cb1ec9a1", size = 226717, upload-time = "2026-10-03T14:55:40.942Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ea/5da9309cc55c2665a6eebc22c369d9918c0d77258c61e92058e6b08d5ff1/websockets-17.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d4df62fd8448a85c752bbea1803cb3a2785e6fc8352009ab64ad7447af079b3c", size = 228413, upload-time = "2026-10-03T14:55:42.54Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/5a24df72aa5500f311105687af864c27f1f9da910e968e97818c6149e6b0/websockets-17.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8eea55fdfa9ba65c6981eea38bd20c800bce2f092a2803d82de764ecf0f071a", size = 227196, upload-time = "2026-10-03T14:55:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ee/ca32cc1ed892dc4ac30a922e8f648048233fbdb8b0bce7048860ec4c60ec/websockets-17.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3f0def1279644acaa9bc861d4234af3f82ea9cee7e460dffac5cb63e691501e9", size = 226092, upload-time = "2026-10-03T14:55:45.842Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/12d4a73324aa9798d5165d20c088f9dba66c75c871960e5d921ec66694e4/websockets-17.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb78fb4158c12f77a934a003006784108a27a6553cfc0c6f10483c9c02e94f48", size = 223486, upload-time = "2026-10-03T14:55:47.45Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a4/7fe15da5abb8f0f61e6a357593f7f2ed55724825b7db0ffe72b5c5fad68d/websockets-17.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:f8969ad228115ad8869b5fed801f899e52ab8ad376fdb165ba4760a277c8258a", size = 226200, upload-time = "2026-10-03T14:55:49.126Z" }, + { url = "https://files.pythonhosted.org/packages/08/b9/4cd3a311f96a2eea0ed458bc01fe2cce42f9cd50aa9e64315dfc855d63a9/websockets-17.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:4a49ca342efc0800e6ae94ed5c9cbdcb319308f75e73c21181e4c24d6710e8dd", size = 224862, upload-time = "2026-10-03T14:55:50.674Z" }, + { url = "https://files.pythonhosted.org/packages/41/b5/22caa3460f75e42bfcc74028870b556d22847ea9a9034aa03986f07f16a9/websockets-17.2-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:06fa3ce9c3154826c33d4395b225b2994aa64f1f3bcd8be8ed932019175d9268", size = 225391, upload-time = "2026-10-03T14:55:52.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/be/8d28f92092076abf1ddfb3206b0ce956120a22e7c3105f6a3029d727deae/websockets-17.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:50644d8715be7e0ec0682f9d7744b63008e199c5e1618a48fa153756a332235f", size = 226545, upload-time = "2026-10-03T14:55:54.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/ff943fa383e540fe17f066cc10a3eeedef26e50fd45aae2bdc6746d6f95a/websockets-17.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:60deca33e584c09e91f70f8b55a0b1de7d671d6a63f051d154920f48bed717c7", size = 224352, upload-time = "2026-10-03T14:55:55.856Z" }, + { url = "https://files.pythonhosted.org/packages/e9/df/1e6c3e06c473c9fd833a5c1620b15e2c3b37647b91b7d41871d20bc098de/websockets-17.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:b5f79366a8d8dbb981d53ba800bb54a95454595ab8a4548c2b95501b32a08326", size = 225255, upload-time = "2026-10-03T14:55:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/f8/d8a4f988f7cbb568d8bd69da4632c5b6010aa9cd9366f285e23b73b678d9/websockets-17.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f2bbf3f28d0b63157577c8b774b9136f076afa6797e1a52a2ecd477f23cad3a8", size = 225513, upload-time = "2026-10-03T14:55:59.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e0/920357165b2797a2530fc9e271d79a9b5fee2b750b154c990c740f767af3/websockets-17.2-cp315-cp315-win32.whl", hash = "sha256:74836317b7010b579522bb52426f1e225608b042c9e78cbe2493522bebb8a318", size = 217722, upload-time = "2026-10-03T14:56:01.307Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/25bdca25bbc329ffb330ef33993397d6556a871e40a0d196e757699ea3f7/websockets-17.2-cp315-cp315-win_amd64.whl", hash = "sha256:aaead3d926e9ab4124ada727d20cd62d396649917822df4f771d1f07f1079b40", size = 218017, upload-time = "2026-10-03T14:56:02.914Z" }, + { url = "https://files.pythonhosted.org/packages/fa/cb/ea30a552bbcd1c75f0d14bfce6c884ee36187030b85b74a242aacc02406e/websockets-17.2-cp315-cp315-win_arm64.whl", hash = "sha256:40960554e60eb60c3eec4ff9e42a80f84f8cd3ca9bc80a5481a61f1e64d807c9", size = 217929, upload-time = "2026-10-03T14:56:04.604Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/477664c619af8aa3c908d482e2a95e13ceed9d78f21d15902013c3bc6c28/websockets-17.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9a2a60a7f0ea5f239efb6391d2b28630a640d82dad63e3bee47cf2c623c4495d", size = 218029, upload-time = "2026-10-03T14:56:06.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a9/b0be62ff1c0e2bc966da56b36d3d820c7e2ad3c0c4a4ac414fc7335b214f/websockets-17.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:cca2fcb72c007103740fa4fc3df19fdb1a318c641c69f3b0cc47ed63a889336e", size = 215607, upload-time = "2026-10-03T14:56:08.035Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/a6738530de0437a31c1b168e4096ecf790aafaf561f33a009886c7d8042e/websockets-17.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b789356bc4e2e6c20ba52817f92c3fed74e24657654237ecd536c54843b80c6c", size = 215817, upload-time = "2026-10-03T14:56:09.852Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/2fc44ddc419cbb09ee1708af3e78d8a4b018db01fc7e4f91bd730e2f8d9e/websockets-17.2-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:222fb626fa15701a850eccc778be17312142b2f6a0e16aea80770b7459adb784", size = 225979, upload-time = "2026-10-03T14:56:11.85Z" }, + { url = "https://files.pythonhosted.org/packages/2e/91/a215b14caa7ea65bc36db81609108899c259503300d1560dae9c70a135e7/websockets-17.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4497e87c34a2d21cbec1227858fec3af8e514dd70c47625557a122fcebc081dc", size = 226250, upload-time = "2026-10-03T14:56:13.548Z" }, + { url = "https://files.pythonhosted.org/packages/65/b9/9406a18e9edf558ed504d2a7679371d0f8107e4ef526c80b154ea4ec9752/websockets-17.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6281c171557ce0e408e19d9a223f22d915117ac38a5a7f32ed83809e7492316c", size = 227579, upload-time = "2026-10-03T14:56:15.143Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/a73af119244f46f5130005d7ab63f1c75890c890141a0ca2adc9d97d4671/websockets-17.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:08d97098644728bd1895caa7ecf3090b8e563d70809870d2adb33a107bd061d0", size = 229205, upload-time = "2026-10-03T14:56:17.086Z" }, + { url = "https://files.pythonhosted.org/packages/c1/92/ccd8e2e921d134a56f1ed4642d276500d9e33b3dc4d6deb63d614b3e53a6/websockets-17.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1fdb8d5a1660307dc6d36d0b7fc725213cbd7f80800904dc4896aa3208b89121", size = 228011, upload-time = "2026-10-03T14:56:18.716Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ef/7d71105d19a7aaab5ff87b9c712f6c1dda44e72ea56aa0e7b777f2fc274b/websockets-17.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18b0a46e5e9b315e2b54ce8c3bafdeef0e1388ca363114fa868e6aab2dc58512", size = 226892, upload-time = "2026-10-03T14:56:20.412Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/87012d628b21e66e699440f39bfa7cc55fae7f52b2c532ab62184a589624/websockets-17.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f115d5d804a2163dd89245710049078b0e726a58c1f44a1f86c2c6e79055d76", size = 224241, upload-time = "2026-10-03T14:56:22.257Z" }, + { url = "https://files.pythonhosted.org/packages/55/f5/495371068b27ee5f7c435187f9dafd62402f195e2c76063bdd4653da1565/websockets-17.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:1d829946a2e7630f92f9d7b45b62f3abe9f393cc2dea6a35edb3988f865e75f2", size = 227076, upload-time = "2026-10-03T14:56:23.909Z" }, + { url = "https://files.pythonhosted.org/packages/18/18/3dce3cc6099be5e044e0fd5d0e0c9931c8e3387511cdec8014a345f619e5/websockets-17.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:6c274fc1572edf7c197094a0eb1887d45fdc95254bc80597dc7599550486c06a", size = 225727, upload-time = "2026-10-03T14:56:25.689Z" }, + { url = "https://files.pythonhosted.org/packages/47/30/57d0c7aaf8d4473926fa8829b8136483f561388d1e747ae71c9f2a83d5fd/websockets-17.2-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:4173a4b8a025ae44313d9d9b4ecf31e886c7b7faf45386d51a8ca4ff2dcf3f2a", size = 226225, upload-time = "2026-10-03T14:56:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9f/9dce1203756756c00b407b9a6b13a7500fcd38f2634d4daa3f65575814ec/websockets-17.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:d8cfe9522ad69b6abb26b413ed1deca43cb915cefc588433d557cb3ae1c783e2", size = 227333, upload-time = "2026-10-03T14:56:28.811Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2f/d3b6b876678ebb03017b7afd7111fe44d54b93f036a80ebb4b481dd1ab74/websockets-17.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:908d81d88bb16141613a6275059b5114656d5c2f0b5400b421d54fe6f1943507", size = 225082, upload-time = "2026-10-03T14:56:30.578Z" }, + { url = "https://files.pythonhosted.org/packages/32/b0/a69b573a5e56d2e7a5dcbb447466f442380cf81515e1cb1220cd626c8042/websockets-17.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:c6590e1eb624ff6b15b872421bc9a10bc6d2057635d69c6cd244ac3f928f85c6", size = 225945, upload-time = "2026-10-03T14:56:32.32Z" }, + { url = "https://files.pythonhosted.org/packages/70/be/a72911dc8e33f74c196012366ce4d99b1a803894a377a1ed0c8e66df9caa/websockets-17.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:61040f6f7da5a279d2f77496c69d51132aba75f701c52bded400d4c639277b18", size = 226241, upload-time = "2026-10-03T14:56:34.142Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a9/02a68c1d8e5572918e0962d3aad881078f73ede43abd9b1336e4efaa8909/websockets-17.2-cp315-cp315t-win32.whl", hash = "sha256:f90bad2839c185a1edf8ee22a257cfc8a39e0e337a0490ab185dfa76ef04d1bd", size = 217847, upload-time = "2026-10-03T14:56:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bf/3d7c33b8d5e7712a60e0149c017ed50394ec5e8cf72e5cb6a1ffaf11a42d/websockets-17.2-cp315-cp315t-win_amd64.whl", hash = "sha256:315551f4ccedbbf9fd4f7e8bf037a5948c976ade0e919ba5d8f581d465f6f725", size = 218169, upload-time = "2026-10-03T14:56:37.79Z" }, + { url = "https://files.pythonhosted.org/packages/27/57/ab34cc6460c5322e6932750fa5c6c64be89e6ee4e2707d13c4e9d3312b25/websockets-17.2-cp315-cp315t-win_arm64.whl", hash = "sha256:0a6220bdf8d5f11af71251a599092d89ac1d6bfac691c7f5951c5b07953947a0", size = 218089, upload-time = "2026-10-03T14:56:39.427Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e2/09ad9cec0fc7e39f983b52f9e49c44f89b7cf7a61d4761fa7fc398f003f9/websockets-17.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2de1ccf298f5c9e0f27113836d742edb95f015eee3148f004ac386f7ba9a05b1", size = 215348, upload-time = "2026-10-03T14:56:41.037Z" }, + { url = "https://files.pythonhosted.org/packages/80/fe/c307b5d8cdf1852d00606a0403502f0ca5cd8a4736550bab70abce09f7e9/websockets-17.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:761cde41439f0be761aa460e1451a31e2e14baf4a46db6fe4913e5a06a90df66", size = 215621, upload-time = "2026-10-03T14:56:43.097Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/af8412f154cd0568afc043ab478cc8c1ebdf9337b25c85cb9a049d18cfcb/websockets-17.2-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:15a7101b660a9f15fac34108c92cefc9848f6753a50acef8869e3cd94148fdb7", size = 216569, upload-time = "2026-10-03T14:56:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/fc/76/92ae57b985378036bb8133ea39d1e5cc4d97accad9cae38169426bdcef75/websockets-17.2-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:214da56dba368f61b3d745c77630b2d03c61c02da7b42fe80ef6efba079d3077", size = 216462, upload-time = "2026-10-03T14:56:46.771Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/e3b276473f7f38984990eb29cf525ffaed131f6136bedb929b5c2ce7151e/websockets-17.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80cbc645af23ac5c12096545c161626960114a1bc10f864760558d3b3e82ba18", size = 217355, upload-time = "2026-10-03T14:56:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a1/459ab96c5cda8a2164f594be6dc9f868de7971e6abafa696ea07534139a6/websockets-17.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:063508ce9e0db745f30ab52fc652f4e59efc79c2b74934b3837d5cdb974da620", size = 218612, upload-time = "2026-10-03T14:56:50.287Z" }, + { url = "https://files.pythonhosted.org/packages/8a/58/835cd51934d6780fa586f275b5d9901eead6d81569b4343b3767cdbaae4c/websockets-17.2-py3-none-any.whl", hash = "sha256:6aa59f0ef92e796b2db6f5f26550c4713c0e4036899fadf02f55e2ed4db0b7ae", size = 211883, upload-time = "2026-10-03T14:56:51.898Z" }, +] diff --git a/evolve_charts/__init__.py b/evolve_charts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/evolve_charts/categories.py b/evolve_charts/categories.py new file mode 100644 index 0000000..a5a624b --- /dev/null +++ b/evolve_charts/categories.py @@ -0,0 +1,41 @@ +"""Evolve's workload categories, and which GPU scope (render pass) belongs to which. + +Evolve times **passes**, single blocks of GPU work. A **workload category** (`WorkloadCategory`) is GPU work +that logically belongs together, and what Evolve scores: all ray tracing work is the Raytracing workload, +whose score is the Ray Tracing score. + +Which pass belongs to which workload category comes from `scope_catalog.py`, generated from Evolve's Rust +source by `tools/sync_categories.py`. Passes Evolve leaves uncategorized are left out of the tool. +""" + +from .scope_catalog import UNCATEGORIZED_PASSES, WORKLOAD_NAMES, WORKLOADS + +WORKLOAD_DESCRIPTIONS = { + "Raytracing": "All ray tracing work: tracing rays for shadows, reflections, lighting and path tracing.", + "Acceleration Structure Builds": "Building and updating the structures (BVHs) that make ray tracing fast.", + "Rasterization": "Drawing triangles to the screen, the classic way of rendering.", + "Compute": "General-purpose GPU programs: denoising, lighting, post-processing, animation, ...", + "Workgraphs": "GPU work that schedules more GPU work by itself (DirectX 12 work graphs).", + "NRC": "The neural radiance cache: a small neural network that learns how light bounces.", + "Upscaling": "Upscaling a lower-resolution image to the output resolution (FSR, XeSS, ...).", +} +# Evolve's order, also used in charts. +WORKLOAD_ORDER = list(WORKLOAD_DESCRIPTIONS) + + +def workloads(scope: str) -> list[str]: + """The workload categories the pass is part of, in Evolve's order; empty if Evolve leaves it uncategorized. + + Almost every pass is part of exactly one; Evolve lists a handful in two. + """ + names = {WORKLOAD_NAMES.get(variant) for variant in WORKLOADS.get(scope, [])} + return [name for name in WORKLOAD_ORDER if name in names] + + +def is_utility(scope: str) -> bool: + """Support work like copies, clears and the user interface, which isn't worth listing as a pass. + + These are the passes Evolve's pass bucketing leaves uncategorized. Some of those still do the work of a + workload category of their own (upscalers, the neural radiance cache) and are kept. + """ + return scope in UNCATEGORIZED_PASSES and set(workloads(scope)) <= {"Compute"} diff --git a/evolve_charts/charts.py b/evolve_charts/charts.py new file mode 100644 index 0000000..edf84e2 --- /dev/null +++ b/evolve_charts/charts.py @@ -0,0 +1,237 @@ +"""Plotly figures with one shared, publication-ready style.""" + +import pandas as pd +import plotly.graph_objects as go + +# Evolve's palette (`evolve/crates/evolve/src/ui/styling.rs`) nudged in lightness and chroma until every +# adjacent pair, and every pair among the first four, stays apart for colour-blind readers on white: +# Evolve green and orange lead, as the two results most comparisons have. +RESULT_COLORS = ["#296127", "#F0582A", "#8A5AA8", "#2E9C94", "#C48A2C", "#3F7FC8", "#7DAA4E", "#B5294A"] + + +GREEN, ORANGE, PURPLE, TEAL, GOLD, BLUE, LIGHT_GREEN, WINE = RESULT_COLORS + +# (colour, hatch) per workload category, fixed so a category looks the same in every chart; every pair of +# neighbours in Evolve's order stays apart for colour-blind readers. +WORKLOAD_STYLES = { + "Raytracing": (ORANGE, ""), + "Acceleration Structure Builds": (PURPLE, ""), + "Rasterization": (GREEN, ""), + "Compute": (TEAL, ""), + "Workgraphs": (GOLD, ""), + "NRC": (WINE, ""), + "Upscaling": (LIGHT_GREEN, ""), +} + +# Evolve's Spartan, loaded into the page from `static/`; downloaded SVGs fall back when it isn't installed. +FONT = "Spartan, Segoe UI, Helvetica, Arial, sans-serif" +# Text on Evolve's white frames: GREEN_DARK for text, GRAY_DIM for secondary text, GRAY_LIGHT for grid lines. +TEXT = "#0F3F1D" +MUTED = "#696969" +GRID = "#EEEAEA" +EXPORT_WIDTH = 1200 + + +def result_colors(labels: list[str]) -> dict[str, str]: + return {label: RESULT_COLORS[index % len(RESULT_COLORS)] for index, label in enumerate(labels)} + + +def style(fig: go.Figure, title: str, subtitle: str, note: str | None, height: int) -> go.Figure: + fig.update_layout( + template="simple_white", + height=height, + paper_bgcolor="#FFFFFF", + plot_bgcolor="#FFFFFF", + font={"family": FONT, "size": 15, "color": TEXT}, + title={ + "text": f"{title}", + "subtitle": {"text": subtitle, "font": {"size": 15, "color": MUTED}}, + "x": 0.015, + "xanchor": "left", + "xref": "container", + "y": 1 - 18 / height, + "yanchor": "top", + "yref": "container", + "font": {"size": 22}, + }, + # Generous side margins: Plotly measures labels before the Spartan web font has loaded. + margin={"l": 60, "r": 60, "t": 95 if subtitle else 65, "b": 130 if note else 60}, + # Beside the plot rather than above it, so long legends never run into the title. + legend={ + "orientation": "v", + "yanchor": "top", + "y": 1.0, + "x": 1.01, + "xanchor": "left", + "title": None, + "traceorder": "normal", + }, + hoverlabel={"font": {"family": FONT}}, + ) + # Plotly sizes the legend before the Spartan web font has loaded; an em space absorbs the wider text. + for trace in fig.data: + if trace.name and not trace.name.endswith("\u2003"): + trace.name += "\u2003" + axis = {"gridcolor": GRID, "linecolor": MUTED, "tickcolor": MUTED, "zerolinecolor": MUTED, "automargin": True} + fig.update_xaxes(**axis) + fig.update_yaxes(**axis) + if note: + fig.add_annotation( + text=note, + xref="paper", + yref="paper", + x=0, + y=0, + yanchor="top", + yshift=-70, + xanchor="left", + align="left", + showarrow=False, + font={"size": 12, "color": MUTED}, + ) + return fig + + +def download_config(filename: str, image_format: str, height: int) -> dict: + return { + "displaylogo": False, + "modeBarButtonsToRemove": ["lasso2d", "select2d"], + "toImageButtonOptions": { + "format": image_format, + "filename": filename, + "width": EXPORT_WIDTH, + "height": height, + "scale": 2 if image_format == "png" else 1, + }, + } + + +def breakdown( + summary: pd.DataFrame, + labels: list[str], + percent: bool, + order: list[str], + styles: dict[str, tuple[str, str]], + descriptions: dict[str, str], +) -> go.Figure: + """Stacked horizontal bar per result, split by category.""" + value = "share_pct" if percent else "mean_ms" + labels = [label for label in labels if label in set(summary["result"])] + fig = go.Figure() + present = [item for item in order if summary.loc[summary["item"] == item, "mean_ms"].sum() > 0] + for item in present: + rows = summary[summary["item"] == item].set_index("result").reindex(labels) + color, hatch = styles.get(item, ("#A8A8A8", "")) + fig.add_bar( + name=item, + y=labels, + x=rows[value], + orientation="h", + marker={ + "color": color, + "pattern": {"shape": hatch, "fgcolor": "#FFFFFF", "solidity": 0.35}, + # A 2px white gap between stacked segments keeps neighbouring categories apart. + "line": {"color": "#FFFFFF", "width": 2}, + }, + customdata=list(zip(rows["mean_ms"], rows["share_pct"])), + hovertemplate=f"{item}
%{{customdata[0]:.2f}} ms per frame (%{{customdata[1]:.1f}}%)" + f"
{_wrap(descriptions.get(item, ''))}%{{y}}", + ) + if not percent: + totals = summary.groupby("result")["mean_ms"].sum().reindex(labels) + for label, total in totals.items(): + fig.add_annotation(x=total, y=label, text=f" {total:.1f} ms", xanchor="left", showarrow=False) + fig.update_layout(barmode="stack", bargap=0.35) + fig.update_yaxes(autorange="reversed", title=None) + fig.update_xaxes(title="Share of GPU time (%)" if percent else "GPU time per frame (ms)") + return fig + + +def score_bars(scores: pd.DataFrame, colors: dict[str, str]) -> go.Figure: + """Grouped horizontal bars: one row per score, one bar per result.""" + fig = go.Figure() + for label in scores.columns: + fig.add_bar( + name=label, + y=scores.index, + x=scores[label], + orientation="h", + marker_color=colors[label], + text=[f"{value:,.0f}" if pd.notna(value) else "" for value in scores[label]], + textposition="outside", + cliponaxis=False, + hovertemplate="%{y}
%{x:,.0f}" + label + "", + ) + fig.update_layout(barmode="group", bargap=0.25, bargroupgap=0.05, barcornerradius=4) + fig.update_yaxes(autorange="reversed", title=None) + fig.update_xaxes(title="Score (higher is better)", rangemode="tozero") + return fig + + +def bars(summary: pd.DataFrame, labels: list[str], colors: dict[str, str], items: list[str]) -> go.Figure: + """Grouped horizontal bars: one row per item, one bar per result, whiskers from p5 to p95.""" + fig = go.Figure() + for label in labels: + rows = summary[summary["result"] == label].set_index("item").reindex(items) + # Results without any of these items (no shadows in path tracing) get no bars or legend entry. + if rows["mean_ms"].isna().all(): + continue + fig.add_bar( + name=label, + y=items, + x=rows["mean_ms"], + orientation="h", + marker_color=colors[label], + error_x={ + "type": "data", + "symmetric": False, + "array": rows["p95_ms"] - rows["mean_ms"], + "arrayminus": rows["mean_ms"] - rows["p5_ms"], + "color": "#888888", + "thickness": 1.2, + "width": 3, + }, + customdata=list(zip(rows["median_ms"], rows["p5_ms"], rows["p95_ms"], rows["ran_in_pct_of_frames"])), + hovertemplate="%{y}
mean %{x:.3f} ms Β· median %{customdata[0]:.3f} ms" + "
5–95% of frames: %{customdata[1]:.3f}–%{customdata[2]:.3f} ms" + "
runs in %{customdata[3]:.0f}% of frames" + label + "", + ) + fig.update_layout(barmode="group", bargap=0.25, bargroupgap=0.05, barcornerradius=4) + fig.update_yaxes(autorange="reversed", title=None) + fig.update_xaxes(title="GPU time per frame (ms)", rangemode="tozero") + return fig + + +def bars_height(items: int, results: int) -> int: + return int(220 + items * max(results, 1) * 22 + items * 10) + + +def lines( + series: dict[str, pd.Series], colors: dict[str, str], y_title: str, unit: str, from_zero: bool = True +) -> go.Figure: + """One line per result over benchmark time. Sensors zoom in (`from_zero=False`) so small drifts show.""" + fig = go.Figure() + for label, values in series.items(): + fig.add_scatter( + name=label, + x=values.index, + y=values.values, + mode="lines", + line={"color": colors[label], "width": 2}, + hovertemplate=f"%{{y:.2f}} {unit} at %{{x:.1f}} s{label}", + ) + fig.update_xaxes(title="Benchmark time (s)") + fig.update_yaxes(title=y_title, rangemode="tozero" if from_zero else "normal") + fig.update_layout(hovermode="x unified") + return fig + + +def _wrap(text: str, width: int = 60) -> str: + lines, line = [], "" + for word in text.split(): + if line and len(line) + len(word) > width: + lines.append(line) + line = word + else: + line = f"{line} {word}".strip() + return "
".join([*lines, line]) diff --git a/evolve_charts/loader.py b/evolve_charts/loader.py new file mode 100644 index 0000000..ade7bc6 --- /dev/null +++ b/evolve_charts/loader.py @@ -0,0 +1,334 @@ +"""Reads every Evolve export into tidy pandas tables. + +Evolve exports three kinds of data, alone or bundled in the score screen's Export zip: + +- **scores** (`scores.csv`): one score per workload category per loop, higher is better. +- **per frame** (`frametimes.csv`, also inside `evolve_results.csv`): GPU time per workload + category, plus GPU sensor readings, for every frame. +- **deep analysis** (`deep_analysis.json`, Evolve Professional): the GPU time of every render pass + in every frame. + +`evolve_results.csv` combines system information, scores and per-frame data in one file. +""" + +import hashlib +import io +import json +import zipfile +from dataclasses import dataclass, field +from pathlib import PurePath + +import pandas as pd + +from .categories import WORKLOAD_ORDER, workloads + +DEEP_ANALYSIS = "deep_analysis.json" + +METADATA_COLUMNS = [ + "Evolve Version", + "System Name", + "CPU", + "GPU", + "OS Name", + "OS Version", + "GPU Driver Version", + "Rendering Backend", +] + +# Per-frame CSV column -> workload category name (as Evolve shows it). +WORKLOAD_COLUMNS = { + "Ray Tracing (ns)": "Raytracing", + "Acceleration Structure Build (ns)": "Acceleration Structure Builds", + "Rasterization (ns)": "Rasterization", + "Compute (ns)": "Compute", + "Workgraphs (ns)": "Workgraphs", + "NRC (ns)": "NRC", + "Upscaling Performance (ns)": "Upscaling", +} + +# Sensor columns in `Result.frames`, filled from deep analysis (GpuMetrics fields) or the per-frame CSV. +METRIC_FIELDS = { + "usage_percentage": "gpu_usage_pct", + "clock_speed_in_mhz": "gpu_clock_mhz", + "vram_clock_speed_in_mhz": "vram_clock_mhz", + "power_usage_in_w": "gpu_power_w", + "board_power_usage_in_w": "board_power_w", + "soc_power_usage_in_w": "soc_power_w", + "voltage_in_mv": "voltage_mv", + "vram_usage_in_mb": "vram_used_mb", + "edge_temperature_in_c": "edge_temp_c", + "hotspot_temperature_in_c": "hotspot_temp_c", +} +FAN_FIELDS = {"Rpm": "fan_rpm", "Percent": "fan_pct"} +METRIC_CSV_COLUMNS = { + # Evolve writes board power into the column it calls "Energy". + "Energy (W)": "board_power_w", + "Gpu Usage (%)": "gpu_usage_pct", + "Clock Speed (Mhz)": "gpu_clock_mhz", + "Vram Clock Speed (Mhz)": "vram_clock_mhz", + "Gpu Voltage (mV)": "voltage_mv", + "Fan Speed (rpm)": "fan_rpm", + "Fan Speed (%)": "fan_pct", + "Edge Temperature (C)": "edge_temp_c", + "Hot Spot Temperature (C)": "hotspot_temp_c", +} +SENSOR_COLUMNS = list(dict.fromkeys([*METRIC_FIELDS.values(), *FAN_FIELDS.values(), *METRIC_CSV_COLUMNS.values()])) + + +class ExportError(Exception): + """A problem with an input file, worded for the person using the tool.""" + + +@dataclass +class Result: + id: str + filename: str + metadata: dict[str, str] = field(default_factory=dict) + # Score name -> score, averaged over loops. Scores Evolve didn't produce for this run are absent. + scores: dict[str, float] = field(default_factory=dict) + # One row per (loop, frame): GPU milliseconds per workload category, one column each. + workloads: pd.DataFrame | None = None + # One row per (loop, frame): sensor readings, and from deep analysis the GPU time sum and span. + frames: pd.DataFrame | None = None + # Deep analysis only. One row per (loop, frame, pass): ms is the pass' own GPU time in that frame; + # top_ms only counts instances not nested inside another scope, so summing it never double-counts. + passes: pd.DataFrame | None = None + # Deep analysis only: total GPU milliseconds per pass that Evolve doesn't assign to a workload category. + left_out: dict[str, float] = field(default_factory=dict) + warnings: list[str] = field(default_factory=list) + + @property + def gpu(self) -> str: + return self.metadata.get("GPU") or self.filename + + @property + def contents(self) -> list[str]: + """What this result contains, in the words the tool uses.""" + return [ + name + for name, present in [ + ("Scores", bool(self.scores)), + ("Per frame", self.workloads is not None), + ("Deep analysis", self.passes is not None), + ] + if present + ] + + +def load_export(data: bytes, filename: str) -> Result: + result = Result(id=hashlib.sha256(data).hexdigest()[:12], filename=filename) + suffix = PurePath(filename).suffix.lower() + if suffix == ".zip": + _read_zip(result, data) + elif suffix == ".csv": + _read_csv(result, data, filename) + elif suffix == ".json": + _read_deep_analysis(result, data, filename) + else: + raise ExportError(f"{filename}: use an Evolve export (.zip, .csv or deep analysis .json).") + + if not result.contents: + raise ExportError(f"{filename} contains no Evolve scores, per-frame data or deep analysis.") + if result.workloads is None and result.passes is not None: + result.workloads = _workloads_from_passes(result.passes, result.frames) + if not result.metadata: + result.warnings.append(f"{filename} has no system information (GPU, driver); it's labelled by file name.") + return result + + +def _read_zip(result: Result, data: bytes) -> None: + try: + archive = zipfile.ZipFile(io.BytesIO(data)) + except zipfile.BadZipFile: + raise ExportError(f"{result.filename} is not a zip file.") from None + members = {PurePath(name).name: name for name in archive.namelist()} + + # The combined CSV holds everything the separate CSVs do, plus system information. + for name in ["evolve_results.csv", "frametimes.csv", "scores.csv"]: + if name in members: + _read_csv(result, archive.read(members[name]), f"{result.filename}/{name}") + if DEEP_ANALYSIS in members: + _read_deep_analysis(result, archive.read(members[DEEP_ANALYSIS]), result.filename) + + +def _read_csv(result: Result, data: bytes, name: str) -> None: + try: + table = pd.read_csv(io.BytesIO(data), encoding="utf-8-sig", dtype=dict.fromkeys(METADATA_COLUMNS, str)) + except (pd.errors.ParserError, pd.errors.EmptyDataError, UnicodeDecodeError): + raise ExportError(f"{name} can't be read as a CSV file.") from None + columns = list(table.columns) + + if "Evolve Version" in columns and not result.metadata: + first = table.iloc[0] + result.metadata = {key: str(first[key]) for key in METADATA_COLUMNS if key in table and pd.notna(first[key])} + if columns[:1] == ["Loop"] or "Evolve Version" in columns: + loop = "Loop" if "Loop" in columns else "Loop Index" + score_columns = columns[1:] if loop == "Loop" else columns[len(_metadata_prefix(columns)) : columns.index(loop)] + if not result.scores: + result.scores = _scores(table.drop_duplicates(loop), score_columns) + if {"Loop Index", "Frame", "Benchmark Time (ns)"} <= set(columns) and result.workloads is None: + result.workloads, sensors = _per_frame(table) + if result.frames is None: + result.frames = sensors + if not columns or not (result.scores or result.workloads is not None or "Evolve Version" in columns): + raise ExportError(f"{name} isn't an Evolve scores or per-frame export.") + + +def _metadata_prefix(columns: list[str]) -> list[str]: + return [column for column in columns if column in METADATA_COLUMNS] + + +def _scores(per_loop: pd.DataFrame, columns: list[str]) -> dict[str, float]: + scores = {} + for column in columns: + values = pd.to_numeric(per_loop[column], errors="coerce") + # Evolve writes 0 for scores it didn't calculate for this benchmark (e.g. Work Graphs). + values = values[values > 0] + if len(values): + scores[column] = float(values.mean()) + return scores + + +def _per_frame(table: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]: + base = pd.DataFrame( + { + "loop": table["Loop Index"].astype(int), + "frame": table["Frame"].astype(int), + "seq_s": table["Benchmark Time (ns)"] / 1e9, + } + ) + workloads = base.copy() + for column, name in WORKLOAD_COLUMNS.items(): + if column in table and table[column].notna().any(): + # Evolve leaves the cell empty when a workload took no time in that frame. + workloads[name] = table[column].fillna(0) / 1e6 + sensors = base.copy() + for column, name in METRIC_CSV_COLUMNS.items(): + sensors[name] = pd.to_numeric(table[column], errors="coerce") if column in table else float("nan") + for name in SENSOR_COLUMNS: + if name not in sensors: + sensors[name] = float("nan") + return workloads, sensors + + +def _read_deep_analysis(result: Result, data: bytes, filename: str) -> None: + text = data.decode("utf-8") + try: + loops = json.loads(text) + except json.JSONDecodeError: + # Evolve only closes the array after the last loop, so an interrupted run misses the final "]". + try: + loops = json.loads(text.rstrip().rstrip(",") + "]") + except json.JSONDecodeError: + raise ExportError(f"{filename}: {DEEP_ANALYSIS} is damaged and can't be read.") from None + result.warnings.append(f"{filename}: the run was interrupted; using the {len(loops)} completed loop(s).") + if not isinstance(loops, list) or (loops and "per_frame_results" not in loops[0]): + raise ExportError(f"{filename} isn't an Evolve deep analysis file.") + + passes, frames, left_out = _tabulate(loops) + if passes.empty: + result.warnings.append( + f"{filename}: its deep analysis has no GPU work that Evolve assigns to a workload category." + ) + return + result.passes, result.frames, result.left_out = passes, frames, left_out + + nested = passes["ms"].sum() - passes["top_ms"].sum() + if nested > 0: + result.warnings.append( + f"{filename}: {nested / passes['ms'].sum():.1%} of measured GPU time is nested inside other passes. " + "Totals only count it once." + ) + + +def _tabulate(loops: list[dict]) -> tuple[pd.DataFrame, pd.DataFrame, dict[str, float]]: + # Pass name -> whether Evolve assigns it to a workload category. + categorized: dict[str, bool] = {} + left_out: dict[str, float] = {} + pass_rows = [] + frame_rows = [] + for loop in loops: + loop_index = loop["loop_index"] + for frame_index, frame in enumerate(loop["per_frame_results"]): + seq_s = frame["sequence_time_ns"] / 1e9 + own, top = {}, {} + left_out_ms = 0.0 + first_start, last_end = None, None + for command_buffer in frame["command_buffer_timings"].values(): + scopes = [] + for name, timings in command_buffer["scope_timings"].items(): + if name not in categorized: + categorized[name] = bool(workloads(name)) + for timing in timings: + start, end = timing["start"], timing["end"] + first_start = start if first_start is None else min(first_start, start) + last_end = end if last_end is None else max(last_end, end) + if not categorized[name]: + left_out[name] = left_out.get(name, 0.0) + (end - start) / 1e6 + left_out_ms += (end - start) / 1e6 + else: + scopes.append((start, end, name)) + + # Sorted by start, longest first: a scope ending before the current outer one is nested in it. + scopes.sort(key=lambda scope: (scope[0], -scope[1])) + outer_end = None + for start, end, name in scopes: + duration = (end - start) / 1e6 + own[name] = own.get(name, 0.0) + duration + if outer_end is None or end > outer_end: + top[name] = top.get(name, 0.0) + duration + outer_end = end + + for name, ms in own.items(): + pass_rows.append((loop_index, frame_index, seq_s, name, ms, top.get(name, 0.0))) + + row = { + "loop": loop_index, + "frame": frame_index, + "seq_s": seq_s, + "gpu_sum_ms": sum(top.values()), + "gpu_span_ms": (last_end - first_start) / 1e6 if first_start is not None else float("nan"), + "left_out_ms": left_out_ms, + } + row.update(_metrics(frame.get("metrics"))) + frame_rows.append(row) + + passes = pd.DataFrame(pass_rows, columns=["loop", "frame", "seq_s", "pass", "ms", "top_ms"]) + + frames = pd.DataFrame(frame_rows) + for column in SENSOR_COLUMNS: + if column not in frames: + frames[column] = float("nan") + return passes, frames, left_out + + +def _workloads_from_passes(passes: pd.DataFrame, frames: pd.DataFrame) -> pd.DataFrame: + """Per-frame GPU time per workload category, the way Evolve sums it for its per-frame export.""" + membership = pd.DataFrame( + [(name, workload) for name in passes["pass"].unique() for workload in workloads(name)], + columns=["pass", "workload"], + ) + if membership.empty: + return frames[["loop", "frame", "seq_s"]].copy() + per_workload = passes.merge(membership, on="pass").pivot_table( + index=["loop", "frame"], columns="workload", values="ms", aggfunc="sum" + ) + table = frames[["loop", "frame", "seq_s"]].join(per_workload, on=["loop", "frame"]) + present = [name for name in WORKLOAD_ORDER if name in table] + return table[["loop", "frame", "seq_s", *present]].fillna({name: 0.0 for name in present}) + + +def _metrics(metrics: dict | None) -> dict[str, float]: + if not metrics: + return {} + values = {} + for key, column in METRIC_FIELDS.items(): + value = metrics.get(key) + if isinstance(value, (int, float)): + values[column] = float(value) + fan = metrics.get("fan_speed") + if isinstance(fan, dict): + for unit, value in fan.items(): + if unit in FAN_FIELDS and isinstance(value, (int, float)): + values[FAN_FIELDS[unit]] = float(value) + return values diff --git a/evolve_charts/scope_catalog.py b/evolve_charts/scope_catalog.py new file mode 100644 index 0000000..b5a71ce --- /dev/null +++ b/evolve_charts/scope_catalog.py @@ -0,0 +1,454 @@ +# Generated by tools/sync_categories.py from +# evolve/crates/evolve-frame-bucketing/src/lib.rs. Do not edit by hand. + +# scope name -> WorkloadCategory variants (Evolve lists a handful of scopes in two) +WORKLOADS = { + "reflection-ray-tracing-pipeline": ["RayTracing"], + "reflection-ray-tracing-inline": ["RayTracing"], + "reflection-specular-occlusion-pipeline": ["RayTracing"], + "reflection-specular-occlusion-inline": ["RayTracing"], + "trace-diffuse-rays-pipeline": ["RayTracing"], + "trace-diffuse-rays": ["RayTracing"], + "trace-diffuse-nee-rays-pipeline": ["RayTracing"], + "trace-diffuse-nee-rays": ["RayTracing"], + "trace-spatial-validation-rays-pipeline": ["RayTracing"], + "trace-spatial-validation-rays-inline": ["RayTracing"], + "diffuse-restir-spatial-validation": ["RayTracing"], + "shadow-ray-tracing-pipeline": ["RayTracing"], + "shadow-raytrace-inline": ["RayTracing"], + "trace-shadow-mask": ["RayTracing"], + "trace-cache-validation-rays-low": ["RayTracing"], + "trace-cache-validation-rays-medium": ["RayTracing"], + "trace-cache-validation-rays-high": ["RayTracing"], + "trace-cache-validation-rays-low-pipeline": ["RayTracing"], + "trace-cache-validation-rays-medium-pipeline": ["RayTracing"], + "trace-cache-validation-rays-high-pipeline": ["RayTracing"], + "trace-cache-diffuse-rays-low": ["RayTracing"], + "trace-cache-diffuse-rays-medium": ["RayTracing"], + "trace-cache-diffuse-rays-high": ["RayTracing"], + "trace-cache-diffuse-rays-low-pipeline": ["RayTracing"], + "trace-cache-diffuse-rays-medium-pipeline": ["RayTracing"], + "trace-cache-diffuse-rays-high-pipeline": ["RayTracing"], + "primary_sampling": ["RayTracing"], + "spatial_search": ["RayTracing"], + "trace_rays": ["RayTracing"], + "trace_shadow_rays": ["RayTracing"], + "trace_shadow_rays_nrc": ["RayTracing"], + "path-tracer-pipeline": ["RayTracing"], + "path-tracer-pipeline-nrc": ["RayTracing"], + "bruteforce-volume-trace-inline": ["RayTracing"], + "bruteforce-volume-trace-inline-dbg": ["RayTracing"], + "path-trace-distance-sampling-inline": ["RayTracing"], + "path-trace-distance-sampling-inline-dbg": ["RayTracing"], + "voxel-tracer-inline": ["RayTracing"], + "voxel-tracer-inline-dbg": ["RayTracing"], + "vol-ris-initial-sampling": ["RayTracing", "Compute"], + "water-tracing-inline": ["RayTracing"], + "water-tracing-inline-max-quality": ["RayTracing"], + "water-tracing-pipeline": ["RayTracing"], + "water-tracing-pipeline-max-quality": ["RayTracing"], + "batch build blases": ["AccelerationStructureBuilds"], + "batch refit blas": ["AccelerationStructureBuilds"], + "Batch refit bottom level": ["AccelerationStructureBuilds"], + "custom batch refit bottom level": ["AccelerationStructureBuilds"], + "compact blas": ["AccelerationStructureBuilds"], + "build tlas": ["AccelerationStructureBuilds"], + "batch build OMMs": ["AccelerationStructureBuilds"], + "mesh-parts-prefix-sum-hierarchy": ["Rasterization"], + "mesh-parts-prefix-sum": ["Rasterization"], + "compute-multi-clear": ["Rasterization"], + "cull-instances": ["Rasterization"], + "raster-buckets-prefix-sum-hierarchy": ["Rasterization"], + "raster-buckets-prefix-sum": ["Rasterization"], + "build-instanced-draw-calls": ["Rasterization"], + "group-instances": ["Rasterization"], + "spd-external-mip-0": ["Rasterization"], + "vbuffer-mark-visible-nodes": ["Rasterization"], + "VBuffer Raster Opaque Pass (indirect)": ["Rasterization"], + "VBuffer Raster Transparent Pass (indirect)": ["Rasterization"], + "VBuffer Decal": ["Rasterization"], + "Water VBuffer Raster Opaque Pass (indirect)": ["Rasterization"], + "Build GBuffers": ["Rasterization"], + "reflection-accumulation-pass": ["Compute"], + "transparency_resolve": ["Compute"], + "diffuse-disocclusion-fix": ["Compute"], + "diffuse-spatial-filter": ["Compute"], + "diffuse-restir-temporal": ["Compute"], + "diffuse-restir-resolve": ["Compute"], + "diffuse-restir-spatial": ["Compute"], + "diffuse-restir-patch-reservoir": ["Compute"], + "diffuse-temporal": ["Compute"], + "diffuse-gi-debug": ["Compute"], + "prepare-diffuse-rays": ["Compute"], + "resolve-diffuse-rays": ["Compute"], + "initialize-allocation-indices": ["Compute"], + "clamp-allocation-indices": ["Compute"], + "update-lifetimes": ["Compute"], + "update-clipmap": ["Compute"], + "build-spherical-harmonics": ["Compute"], + "visualize-new-irradiance-cache-depth-project": ["Compute"], + "purge-entries-fill-indirect-args": ["Compute"], + "purge-entries": ["Compute"], + "prepare-cache-indirect-args": ["Compute"], + "clear-stale-data": ["Compute"], + "init-path-data": ["Compute"], + "propagate-light": ["Compute"], + "resolve-cache": ["Compute"], + "inject-radiance-for-diffuse-gi": ["Compute"], + "generate_camera_ray": ["Compute"], + "fill_indirect_args": ["Compute"], + "clear-all-counters": ["Compute"], + "shading-basic": ["Compute"], + "resolve": ["Compute"], + "apply_nrc": ["Compute"], + "shading-nrc": ["Compute"], + "prepare_wg_records": ["Compute"], + "prepare_wg_indirect_args": ["Compute"], + "prepare_wg_bounce_indirect_args": ["Compute"], + "resolve_wg_pt": ["Compute"], + "reflection-dir-gen": ["Compute"], + "reflection-dir-binning": ["Compute"], + "reflection-temporal-restir": ["Compute"], + "reflection-upscale-restir": ["Compute"], + "reflection-resolve-restir": ["Compute"], + "reflection-temporal-filter": ["Compute"], + "reflection-spatial-filter": ["Compute"], + "reflection-hits-shading": ["Compute"], + "inject-radiance-for-reflections": ["Compute"], + "reprojection_texture": ["Compute"], + "count-neural-materials": ["Compute"], + "gen-buckets-offsets-and-indirect-args": ["Compute"], + "populate-buckets": ["Compute"], + "neubtf-sun-direct-lighting": ["Compute"], + "compositing": ["Compute"], + "compositing-part-2": ["Compute"], + "sun-direct-lighting": ["Compute"], + "set-sundisk-data": ["Compute"], + "copy_depth_to_texture": ["Compute"], + "spd": ["Compute"], + "shadow_prepare": ["Compute"], + "shadow_tile_classification": ["Compute"], + "shadow_filter_pass_0": ["Compute"], + "shadow_filter_pass_1": ["Compute"], + "shadow_filter_pass_2": ["Compute"], + "hard_shadows_resolve": ["Compute"], + "mesh_light_tree_generation": ["Compute"], + "mesh_light_cdf_generation": ["Compute"], + "write_indirect_args": ["Compute"], + "tree_generation": ["Compute"], + "cdf_generation": ["Compute"], + "generate_lightbags": ["Compute"], + "temporal_reuse": ["Compute"], + "copy_reservoirs": ["Compute"], + "spatial_reuse": ["Compute"], + "restir-shading": ["Compute"], + "converge_texture": ["Compute"], + "gtao": ["Compute"], + "gtao-filter": ["Compute"], + "gtao-temporal": ["Compute"], + "luminance": ["Compute"], + "average-luminance": ["Compute"], + "build-loglum-histogram": ["Compute"], + "blur": ["Compute"], + "resolve_bloom": ["Compute"], + "exposure": ["Compute"], + "resolve_exposure": ["Compute"], + "spot-metering": ["Compute"], + "taa": ["Compute"], + "post": ["Compute"], + "apply_skeleton_parenting": ["Compute"], + "apply_node_animation": ["Compute"], + "apply_bone_animation": ["Compute"], + "apply_skeleton_hierarchy": ["Compute"], + "update_bones": ["Compute"], + "apply_vertex_bones": ["Compute"], + "reduce_aabbs": ["Compute"], + "channel_animation": ["Compute"], + "visualize-hierarchy-transforms": ["Compute"], + "override_transforms": ["Compute"], + "calculate_world_transforms": ["Compute"], + "calculate_inverse_transpose_transforms": ["Compute"], + "build-per-draw-instance-data": ["Compute"], + "cs-build-blas-instance-descs": ["Compute"], + "cs-update-blas-instance-transforms": ["Compute"], + "cs-update-blas-instance-data": ["Compute"], + "cs-update-blas-instance-blas-handles": ["Compute"], + "patch-mesh-aabbs": ["Compute"], + "find_grid_cell": ["Compute"], + "compute_cell_sizes": ["Compute"], + "sort_to_blocks": ["Compute"], + "sum_grid": ["Compute"], + "solve_grid": ["Compute"], + "grid_to_boids": ["Compute"], + "update_boid_transforms": ["Compute"], + "patch_buffer": ["Compute"], + "trace-transmittance-cache": ["Compute"], + "blur-transmittance-cache": ["Compute"], + "trace-ambient-cache": ["Compute"], + "vol-ris-final-shading": ["Compute"], + "output-accumulator": ["Compute"], + "output-upscaler": ["Compute"], + "voxel-debug-rendering": ["Compute"], + "slicer-fill": ["Compute"], + "dof-setup": ["Compute"], + "dof-coc-flatten": ["Compute"], + "dof-dilate": ["Compute"], + "dof-downpresort": ["Compute"], + "dof-main": ["Compute"], + "dof-median9": ["Compute"], + "dof-composite": ["Compute"], + "dof-downscale": ["Compute"], + "dof-compute-near-mask": ["Compute"], + "dof-dilate-near-mask": ["Compute"], + "dof-blur": ["Compute"], + "dof-flood-fill": ["Compute"], + "sss-blur": ["Compute"], + "sss-compositing": ["Compute"], + "Final blit": ["Compute"], + "DMA clear texture": ["Compute"], + "Fill buffer": ["Compute"], + "compute-clear": ["Compute"], + "Copy buffer to buffer": ["Compute"], + "calculate_node_aabbs": ["Compute"], + "lod-designation": ["Compute"], + "generate-water-normals": ["Compute"], + "water-fetch-normals": ["Compute"], + "water-shading": ["Compute"], + "water-shading-max-quality": ["Compute"], + "find_wind_affected_nodes": ["Compute"], + "generate_procedural_instance_info": ["Compute"], + "advance_wind_parameters": ["Compute"], + "apply_wind_to_vertices": ["Compute"], + "resolve_procedural_aabbs": ["Compute"], + "nrc-extract-vertex-positions": ["Compute"], + "nrc-gather-and-encode-auxiliary": ["Compute"], + "nrc-validate-training-paths": ["Compute"], + "nrc-gather-and-extract-positions": ["Compute"], + "nrc-radiance-operator-forward": ["Compute"], + "nrc-propagate-radiance": ["Compute"], + "nrc-loss-backward": ["Compute"], + "nrc-radiance-operator-backward": ["Compute"], + "random_init": ["Compute"], + "latent-grid-forward": ["Compute"], + "concat-forward": ["Compute"], + "gather-forward": ["Compute"], + "linear-transpose-next-activation-backward": ["Compute"], + "linear-backward-input": ["Compute"], + "concat-backward-lhs": ["Compute"], + "latent-grid-backward": ["Compute"], + "fpp-to-f16": ["Compute"], + "linear-transpose-prev-activation-forward": ["Compute"], + "adam-step": ["Compute"], + "linear-backward-weight-small": ["Compute"], + "nrc-ema": ["Compute"], + "nrc-fill-indirect-args": ["Compute"], + "gen-indirect-args": ["Compute"], + "element-wise-gen-indirect-args": ["Compute"], + "Convert Linear Algebra Matrix": ["Compute"], + "triangle-wave-encoding-forward": ["Compute"], + "nrc-pt-apply-nrc": ["Compute"], + "nrc-wfpt-apply-nrc": ["Compute"], + "fused-mlp-forward-inference-64": ["Compute"], + "fused-mlp-forward-inference-32": ["Compute"], + "fused-mlp-forward-inference-16": ["Compute"], + "fused-mlp-forward-training-64": ["Compute"], + "fused-mlp-forward-training-16": ["Compute"], + "fused-mlp-backward-64": ["Compute"], + "fused-mlp-backward-32": ["Compute"], + "fused-mlp-backward-16": ["Compute"], + "sky-cubemap": ["Compute"], + "clear-counters": ["Compute"], + "Copy buffer to texture": ["Compute"], + "Copy buffer to texture (subresource)": ["Compute"], + "Clear buffer pipeline": ["Compute"], + "Clear texture pipeline": ["Compute"], + "generate_paths": ["Workgraphs"], + "process_bounce": ["Workgraphs"], + "fused-mlp-forward-inference-coopvec-48-32-4": ["Nrc"], + "fused-mlp-forward-inference-coopvec-32-32-8": ["Nrc"], + "fused-mlp-forward-inference-coopvec-32-64-8": ["Nrc"], + "fused-mlp-forward-training-coopvec-48-32-4": ["Nrc"], + "fused-mlp-forward-training-coopvec-32-32-8": ["Nrc"], + "fused-mlp-forward-training-coopvec-32-64-8": ["Nrc"], + "fused-mlp-backward-coopvec-48-32-4": ["Nrc"], + "fused-mlp-backward-coopvec-32-32-8": ["Nrc"], + "fused-mlp-backward-coopvec-32-64-8": ["Nrc"], + "fused-mlp-forward-inference-coopmat-32-32-8": ["Nrc"], + "fused-mlp-forward-training-32": ["Nrc"], + "Fsr execute": ["UpscalingPerformance"], + "Xess execute": ["UpscalingPerformance"], + "DLSS evaluate": ["UpscalingPerformance"], + "NSS Upscale Pass": ["UpscalingPerformance"], + "upscaling-pipeline-sample-easu": ["UpscalingPerformance"], + "Upscaling raster pass": ["UpscalingPerformance"], + "Upscale - sharpening pass": ["UpscalingPerformance"], + "Msaa resolve": ["Uncategorized"], + "visualize-hierarchy": ["Uncategorized"], + "visualize-skeletons": ["Uncategorized"], + "egui_render": ["Uncategorized"], + "scale-raster": ["Uncategorized"], + "patch_freelist_buffer": ["Uncategorized"], + "pick_mesh": ["Uncategorized"], + "pick_mesh_rt_inline": ["Uncategorized"], + "trace-mesh-selection": ["Uncategorized"], + "uv_mask": ["Uncategorized"], + "distance_field": ["Uncategorized"], + "merge_outlines": ["Uncategorized"], + "Draw Mesh Selection": ["Uncategorized"], + "breda-manipulators action triangles": ["Uncategorized"], + "breda-manipulators draw triangles": ["Uncategorized"], + "breda-manipulators action lines": ["Uncategorized"], + "breda-manipulators-pick": ["Uncategorized"], + "breda-manipulators draw lines": ["Uncategorized"], + "reflection-preintegrate": ["Uncategorized"], + "Write multiple draws to Metal Indirect Command Buffer": ["Uncategorized"], + "flip-srgb-to-yycxcz": ["Uncategorized"], + "flip-color-filter-horizontal": ["Uncategorized"], + "flip-feature-filter-horizontal": ["Uncategorized"], + "flip-color-filter-vertical": ["Uncategorized"], + "flip-feature-filter-vertical": ["Uncategorized"], + "flip-compare": ["Uncategorized"], + "ssim-filter-horizontal": ["Uncategorized"], + "ssim-filter-vertical": ["Uncategorized"], + "error-to-color": ["Uncategorized"], + "error-histogram": ["Uncategorized"], + "reduce-texture-to-sums": ["Uncategorized"], + "mean-reduce-sums": ["Uncategorized"], + "box_downsample": ["Uncategorized"], + "crop_center": ["Uncategorized"], + "fallback-upscaler": ["Uncategorized"], +} + +# variant -> the name Evolve shows +WORKLOAD_NAMES = { + "RayTracing": "Raytracing", + "AccelerationStructureBuilds": "Acceleration Structure Builds", + "Rasterization": "Rasterization", + "Compute": "Compute", + "Workgraphs": "Workgraphs", + "Nrc": "NRC", + "UpscalingPerformance": "Upscaling", + "Uncategorized": "Uncategorized", +} + +# Scopes Evolve's pass bucketing (BucketPassCategory) leaves uncategorized +UNCATEGORIZED_PASSES = { + "upscaling-pipeline-sample-easu", + "Upscaling raster pass", + "Upscale - sharpening pass", + "Fsr execute", + "Xess execute", + "DLSS evaluate", + "NSS Upscale Pass", + "nrc-extract-vertex-positions", + "nrc-gather-and-encode-auxiliary", + "nrc-validate-training-paths", + "nrc-gather-and-extract-positions", + "nrc-radiance-operator-forward", + "nrc-propagate-radiance", + "nrc-loss-backward", + "nrc-radiance-operator-backward", + "random_init", + "latent-grid-forward", + "concat-forward", + "gather-forward", + "linear-transpose-next-activation-backward", + "linear-backward-input", + "concat-backward-lhs", + "latent-grid-backward", + "fpp-to-f16", + "linear-transpose-prev-activation-forward", + "adam-step", + "linear-backward-weight-small", + "nrc-ema", + "nrc-fill-indirect-args", + "gen-indirect-args", + "element-wise-gen-indirect-args", + "Convert Linear Algebra Matrix", + "triangle-wave-encoding-forward", + "nrc-pt-apply-nrc", + "nrc-wfpt-apply-nrc", + "fused-mlp-backward-64", + "fused-mlp-backward-32", + "fused-mlp-backward-16", + "fused-mlp-forward-inference-64", + "fused-mlp-forward-inference-32", + "fused-mlp-forward-inference-16", + "fused-mlp-forward-training-64", + "fused-mlp-forward-training-16", + "mesh_light_tree_generation", + "mesh_light_cdf_generation", + "write_indirect_args", + "tree_generation", + "cdf_generation", + "scale-raster", + "clear-counters", + "sky-cubemap", + "set-sundisk-data", + "Fill buffer", + "DMA clear texture", + "compute-clear", + "Copy buffer to buffer", + "Copy buffer to texture", + "Copy buffer to texture (subresource)", + "Clear texture pipeline", + "Clear buffer pipeline", + "Final blit", + "egui_render", + "patch_freelist_buffer", + "transparency_resolve", + "Msaa resolve", + "reflection-preintegrate", + "visualize-skeletons", + "pick_mesh", + "pick_mesh_rt_inline", + "trace-mesh-selection", + "uv_mask", + "distance_field", + "merge_outlines", + "Draw Mesh Selection", + "breda-manipulators action triangles", + "breda-manipulators draw triangles", + "breda-manipulators action lines", + "breda-manipulators-pick", + "breda-manipulators draw lines", + "generate_lightbags", + "primary_sampling", + "temporal_reuse", + "copy_reservoirs", + "spatial_search", + "spatial_reuse", + "restir-shading", + "converge_texture", + "bruteforce-volume-trace-inline", + "bruteforce-volume-trace-inline-dbg", + "trace-transmittance-cache", + "blur-transmittance-cache", + "trace-ambient-cache", + "path-trace-distance-sampling-inline", + "path-trace-distance-sampling-inline-dbg", + "voxel-tracer-inline", + "voxel-tracer-inline-dbg", + "vol-ris-initial-sampling", + "vol-ris-final-shading", + "output-accumulator", + "output-upscaler", + "voxel-debug-rendering", + "slicer-fill", + "Write multiple draws to Metal Indirect Command Buffer", + "flip-srgb-to-yycxcz", + "flip-color-filter-horizontal", + "flip-feature-filter-horizontal", + "flip-color-filter-vertical", + "flip-feature-filter-vertical", + "flip-compare", + "ssim-filter-horizontal", + "ssim-filter-vertical", + "error-to-color", + "error-histogram", + "reduce-texture-to-sums", + "mean-reduce-sums", + "box_downsample", + "crop_center", + "fallback-upscaler", +} diff --git a/evolve_charts/stats.py b/evolve_charts/stats.py new file mode 100644 index 0000000..2c96087 --- /dev/null +++ b/evolve_charts/stats.py @@ -0,0 +1,211 @@ +"""Turns loaded results into the numbers the charts show. Everything here is plain pandas. + +Two levels of detail: `"workload"` (Evolve's workload categories, the GPU work it scores) and +`"pass"` (the passes they are made of). +""" + +from typing import Literal + +import pandas as pd + +from .categories import WORKLOAD_ORDER, workloads +from .loader import Result + +Level = Literal["workload", "pass"] +# Narrows passes to one workload category, e.g. ("workload", "Raytracing"). +Within = tuple[Literal["workload"], str] | None + +# Sensor column -> (label, unit) in the order they're listed. +TELEMETRY = { + "gpu_clock_mhz": ("GPU clock", "MHz"), + "power_w": ("GPU power", "W"), + "edge_temp_c": ("GPU temperature", "Β°C"), + "hotspot_temp_c": ("GPU hotspot temperature", "Β°C"), + "gpu_usage_pct": ("GPU usage", "%"), + "vram_clock_mhz": ("Memory clock", "MHz"), + "vram_used_mb": ("VRAM used", "MB"), + "fan_rpm": ("Fan speed", "RPM"), + "fan_pct": ("Fan speed", "%"), + "voltage_mv": ("GPU voltage", "mV"), +} + + +def has_level(result: Result, level: Level) -> bool: + if level == "workload": + return result.workloads is not None and len(result.workloads.columns) > 3 + return result.passes is not None + + +def trimmed_frames(result: Result, skip_s: float) -> pd.DataFrame: + frames = result.frames[result.frames["seq_s"] >= skip_s].copy() + # Board power is what a wall meter sees; fall back to chip or SoC power where that's all there is. + frames["power_w"] = frames["board_power_w"].fillna(frames["gpu_power_w"]).fillna(frames["soc_power_w"]) + return frames + + +def per_frame(result: Result, skip_s: float, level: Level, within: Within = None) -> pd.DataFrame: + """GPU milliseconds per frame, one column per item; frames where an item didn't run count as 0.""" + if level == "workload": + table = result.workloads[result.workloads["seq_s"] >= skip_s] + return table.set_index(["loop", "frame"]).drop(columns="seq_s") + + passes = result.passes[result.passes["seq_s"] >= skip_s] + if within is not None: + _, name = within + passes = passes[passes["pass"].map(lambda scope: name in workloads(scope))] + wide = passes.pivot_table(index=["loop", "frame"], columns="pass", values="ms", aggfunc="sum") + frame_index = trimmed_frames(result, skip_s).set_index(["loop", "frame"]).index + return wide.reindex(frame_index).fillna(0.0) + + +def summary(results: dict[str, Result], skip_s: float, level: Level, within: Within = None) -> pd.DataFrame: + """One row per (result, item) with mean/median/p5/p95 milliseconds per frame. + + Results without data for the level (e.g. no deep analysis) are left out. + """ + rows = [] + for label, result in results.items(): + if not has_level(result, level): + continue + wide = per_frame(result, skip_s, level, within) + total = per_frame(result, skip_s, level).sum(axis=1).mean() if within else wide.sum(axis=1).mean() + for item in wide.columns: + column = wide[item] + rows.append( + { + "result": label, + "item": item, + "workload": ", ".join(workloads(item)) if level == "pass" else "", + "mean_ms": column.mean(), + "median_ms": column.median(), + "p5_ms": column.quantile(0.05), + "p95_ms": column.quantile(0.95), + "share_pct": 100 * column.mean() / total if total else float("nan"), + "ran_in_pct_of_frames": 100 * (column > 0).mean(), + } + ) + columns = [ + "result", + "item", + "workload", + "mean_ms", + "median_ms", + "p5_ms", + "p95_ms", + "share_pct", + "ran_in_pct_of_frames", + ] + return pd.DataFrame(rows, columns=columns) + + +def ordered(items, level: Level) -> list[str]: + """Items in Evolve's order for categories; passes keep the order they were given in.""" + order = WORKLOAD_ORDER if level == "workload" else None + items = list(dict.fromkeys(items)) + return [item for item in order if item in items] if order else items + + +def frame_totals(results: dict[str, Result], skip_s: float) -> pd.DataFrame: + """Deep analysis only: GPU time per frame of all categorized passes, what was left out, and the span.""" + rows = [] + for label, result in results.items(): + if result.passes is None: + continue + frames = trimmed_frames(result, skip_s) + rows.append( + { + "result": label, + "gpu_sum_ms": frames["gpu_sum_ms"].mean(), + "left_out_ms": frames["left_out_ms"].mean(), + "gpu_span_ms": frames["gpu_span_ms"].mean(), + } + ) + return pd.DataFrame(rows, columns=["result", "gpu_sum_ms", "left_out_ms", "gpu_span_ms"]) + + +def timeline( + result: Result, skip_s: float, level: Level, items: list[str], smooth_s: float, within: Within = None +) -> pd.DataFrame: + """Per-frame milliseconds over benchmark time, averaged over loops by frame index.""" + wide = per_frame(result, skip_s, level, within).reindex(columns=items, fill_value=0.0) + source = result.workloads if level == "workload" else trimmed_frames(result, skip_s) + wide["seq_s"] = source.set_index(["loop", "frame"])["seq_s"] + return _smooth(wide.groupby(level="frame").mean().set_index("seq_s"), smooth_s) + + +def telemetry_timeline(result: Result, skip_s: float, smooth_s: float) -> pd.DataFrame: + frames = trimmed_frames(result, skip_s) + columns = [column for column in TELEMETRY if frames[column].notna().any()] + return _smooth(frames.groupby("frame")[["seq_s", *columns]].mean().set_index("seq_s"), smooth_s) + + +def telemetry_summary(results: dict[str, Result], skip_s: float) -> pd.DataFrame: + rows = [] + for label, result in results.items(): + if result.frames is None: + continue + frames = trimmed_frames(result, skip_s) + row = {"result": label} + for column, (name, unit) in TELEMETRY.items(): + if frames[column].notna().any(): + row[f"{name} avg ({unit})"] = frames[column].mean() + row[f"{name} max ({unit})"] = frames[column].max() + rows.append(row) + return pd.DataFrame(rows).set_index("result") + + +def scores_table(results: dict[str, Result]) -> pd.DataFrame: + """Scores as rows (in the order Evolve exports them), results as columns.""" + names = list(dict.fromkeys(name for result in results.values() for name in result.scores)) + return pd.DataFrame( + {label: [result.scores.get(name) for name in names] for label, result in results.items() if result.scores}, + index=names, + ) + + +def comparison_table(item_summary: pd.DataFrame, labels: list[str], baseline: str) -> pd.DataFrame: + """One row per item: mean ms per result, then each other result's % difference from the baseline.""" + labels = [label for label in labels if label in set(item_summary["result"])] + wide = item_summary.pivot_table(index=["item", "workload"], columns="result", values="mean_ms").reindex( + columns=labels + ) + table = wide.copy() + for label in labels: + if label != baseline and baseline in wide: + table[f"{label} vs {baseline}"] = 100 * (wide[label] - wide[baseline]) / wide[baseline] + return table.reset_index() + + +def key_numbers(item_summary: pd.DataFrame, item: str, baseline: str) -> str: + """A sentence a journalist can quote, e.g. "Shadows Tracing: 1.44 ms per frame on A, 0.80 ms on B (44% less)".""" + rows = item_summary[item_summary["item"] == item].set_index("result")["mean_ms"] + if rows.empty or baseline not in rows: + return "" + base = rows[baseline] + parts = [f"{base:.2f} ms per frame on {baseline}"] + for label, ms in rows.items(): + if label == baseline: + continue + if base > 0: + change = 100 * (ms - base) / base + relation = f"{abs(change):.0f}% {'more' if change > 0 else 'less'}" if abs(change) >= 0.5 else "the same" + parts.append(f"{ms:.2f} ms on {label} ({relation})") + else: + parts.append(f"{ms:.2f} ms on {label}") + return f"{item}: " + ", ".join(parts) + "." + + +def wide_for_spreadsheets(item_summary: pd.DataFrame, value: str = "mean_ms") -> pd.DataFrame: + """Items as rows, results as columns: the shape Datawrapper and Excel charts expect.""" + return item_summary.pivot_table(index=["workload", "item"], columns="result", values=value).reset_index() + + +def _smooth(by_time: pd.DataFrame, smooth_s: float) -> pd.DataFrame: + """Centred rolling mean over `smooth_s` seconds of benchmark time; exports differ a lot in frames per second.""" + if smooth_s <= 0 or len(by_time) < 2: + return by_time + step = pd.Series(by_time.index).diff().median() + window = round(smooth_s / step) if step > 0 else 1 + if window <= 1: + return by_time + return by_time.rolling(window, center=True, min_periods=1).mean() diff --git a/evolve_charts/ui.py b/evolve_charts/ui.py new file mode 100644 index 0000000..ba1b9fe --- /dev/null +++ b/evolve_charts/ui.py @@ -0,0 +1,762 @@ +"""The Streamlit app: a page per question, with loaded results kept for the whole session. + +Started by `evolve-charts.py`. Pages: + +- Results: load and name exports, see what each contains and how Evolve's measurements relate. +- Scores, GPU time, Passes, GPU sensors: the analysis, with the loaded results and the + "compare against" choice in a bar at the top. +- Export: CSV files for spreadsheets and newsroom chart tools. +""" + +import os +import re +from dataclasses import dataclass +from pathlib import Path + +import pandas as pd +import streamlit as st + +from . import charts, stats +from .categories import WORKLOAD_DESCRIPTIONS, WORKLOAD_ORDER, is_utility, workloads +from .loader import ExportError, Result, load_export + +APP_DIR = Path(__file__).resolve().parent.parent +STATIC_DIR = APP_DIR / "static" +# Written into the download by tools/package.py; a git checkout has none. +VERSION_FILE = APP_DIR / "VERSION" +# The first second includes one-off work like building the scene's ray tracing structures. +WARM_UP_S = 1.0 +SMOOTHING_S = 1.0 +CARDS_PER_ROW = 4 +DEFAULT_WORKLOAD = "Raytracing" +TOP_PASSES = 15 + +# The workload category each score is the score of; the last few score something other than GPU work. +SCORE_WORKLOADS = { + "Ray Tracing": "Raytracing", + "Acceleration Structure": "Acceleration Structure Builds", + "Rasterization": "Rasterization", + "Compute": "Compute", + "Work Graphs": "Workgraphs", + "NRC": "NRC", + "Upscaler Performance": "Upscaling", + "Driver": "Not GPU work: CPU time in the graphics driver", + "Energy Consumption": "Not GPU work: power use", + "Image Quality 1": "Not GPU work: upscaled image quality (FLIP)", + "Image Quality 2": "Not GPU work: upscaled image quality (SSIM)", +} + +CSS = """ + +""" + +CONCEPTS = """ +
+
PassesSingle blocks of GPU work, timed every frame. + For example shadow-raytrace-inline.
+
β†’
+
Workload categories = scoresGPU work that logically belongs together, and + what Evolve scores. All ray tracing work, such as shadow-raytrace-inline, is the Raytracing workload: + the Ray Tracing score. Higher is better; there is no total score.
+
+""" + +HOW_IT_WORKS = f""" +**Per frame.** A pass' time is the sum of all its executions in a frame; frames where it didn't run count as +0. Averages cover every frame of every loop; the first {WARM_UP_S:g} s are skipped because they contain +one-off setup work. Lines over time are smoothed over {SMOOTHING_S:g} s. + +**Left out.** Passes Evolve doesn't assign to a workload category (copies, clears, the user interface, …) +and idle time between passes. + +**Compare fairly.** Only compare runs of the same benchmark and Evolve version. +""" + + +@dataclass +class Context: + results: dict[str, Result] + labels: list[str] + colors: dict[str, str] + baseline: str + image_format: str + note: str | None + + +def main() -> None: + st.set_page_config(page_title="Evolve Charts", page_icon=str(STATIC_DIR / "evolve-icon.png"), layout="wide") + st.markdown(CSS, unsafe_allow_html=True) + st.logo(str(STATIC_DIR / "evolve-logo.png"), size="large") + + results = _loaded_results() + st.session_state["results"] = results + analysis = [ + st.Page(_scores_page, title="Scores", icon=":material/leaderboard:", url_path="scores"), + st.Page(_gpu_time_page, title="GPU time", icon=":material/stacked_bar_chart:", url_path="gpu-time"), + st.Page(_passes_page, title="Passes", icon=":material/list:", url_path="passes"), + st.Page(_sensors_page, title="GPU sensors", icon=":material/thermostat:", url_path="sensors"), + st.Page(_export_page, title="Export", icon=":material/download:", url_path="export"), + ] + results_page = st.Page(_results_page, title="Results", icon=":material/folder_open:", default=True) + page = st.navigation([results_page, *analysis] if results else [results_page], position="top") + + if page.title != "Results": + st.session_state["context"] = _context_bar(results) + page.run() + + +# Loading --------------------------------------------------------------------------------------------- + + +@st.cache_data(show_spinner=False, max_entries=32) +def _load_file(path: str, modified: float) -> Result: + return load_export(Path(path).read_bytes(), Path(path).name) + + +def _exports_dir() -> Path: + return Path(os.environ.get("EVOLVE_CHARTS_EXPORTS", Path(__file__).resolve().parent.parent / "exports")) + + +def _store() -> dict: + """Session state: uploaded results, load errors, removed results and names, kept across pages.""" + for key, value in [("uploaded", {}), ("errors", {}), ("removed", set()), ("labels", {}), ("upload_key", 0)]: + st.session_state.setdefault(key, value) + return st.session_state + + +def _on_upload() -> None: + store = _store() + for upload in st.session_state.get(f"uploader-{store['upload_key']}") or []: + try: + with st.spinner(f"Reading {upload.name}…"): + result = load_export(upload.getvalue(), upload.name) + except ExportError as error: + store["errors"][upload.name] = str(error) + continue + store["errors"].pop(upload.name, None) + store["uploaded"][result.id] = result + store["removed"].discard(result.id) + # A fresh uploader: the files now live on as result cards. + store["upload_key"] += 1 + + +def _loaded_results() -> dict[str, Result]: + store = _store() + loaded: list[Result] = [] + folder = _exports_dir() + paths = sorted(folder.iterdir()) if folder.is_dir() else [] + for path in paths: + if path.suffix.lower() not in {".zip", ".csv", ".json"}: + continue + try: + with st.spinner(f"Reading {path.name}…"): + loaded.append(_load_file(str(path), path.stat().st_mtime)) + store["errors"].pop(path.name, None) + except ExportError as error: + store["errors"][path.name] = str(error) + loaded += store["uploaded"].values() + + results: dict[str, Result] = {} + seen = set() + for result in loaded: + if result.id in seen or result.id in store["removed"]: + continue + seen.add(result.id) + label = store["labels"].get(result.id) or _default_label(result, loaded) + while label in results: + label += " (2)" + results[label] = result + return results + + +def _default_label(result: Result, loaded: list[Result]) -> str: + """The GPU name; when several results share a GPU, what tells their file names apart instead.""" + if sum(other.gpu == result.gpu for other in loaded) == 1: + return result.gpu + # "evolve_results_20261006_140239_ray-tracing-inline" -> "Ray tracing inline" + stem = re.sub(r"^evolve_results_\d{8}_\d{6}_?", "", Path(result.filename).stem) + return stem.replace("-", " ").replace("_", " ").strip().capitalize() or Path(result.filename).stem + + +# Shared page parts ----------------------------------------------------------------------------------- + + +def _context_bar(results: dict[str, Result]) -> Context: + """The loaded results, what to compare against and chart download settings, above every analysis page.""" + labels = list(results) + colors = charts.result_colors(labels) + chips, baseline_column, downloads, about = st.columns([4, 3, 1, 1], vertical_alignment="center") + chips.markdown( + '
' + + "".join( + f'{label}' + for label in labels + ) + + "
", + unsafe_allow_html=True, + ) + baseline = baseline_column.selectbox( + "Compare against", + labels, + key="baseline", + format_func=lambda label: f"Compare against: {label}", + label_visibility="collapsed", + help="Differences are calculated relative to this result.", + ) + with downloads.popover("Charts", icon=":material/download:", width="stretch"): + st.caption("Hover over a chart and click πŸ“· in its top-right corner to download it.") + image_format = st.radio("Format", ["png", "svg"], horizontal=True, format_func=str.upper, key="format") + show_note = st.toggle("Source line on charts", value=True, key="note", help="GPUs, drivers, Evolve version") + with about.popover("Help", icon=":material/help:", width="stretch"): + st.markdown(CONCEPTS, unsafe_allow_html=True) + st.markdown(HOW_IT_WORKS) + st.caption(_version()) + st.divider() + return Context(results, labels, colors, baseline, image_format, _source_note(results) if show_note else None) + + +def _context() -> Context: + return st.session_state["context"] + + +def _title(title: str, lead: str) -> None: + st.markdown(f"## {title}") + st.markdown(f'

{lead}

', unsafe_allow_html=True) + + +def _source_note(results: dict[str, Result]) -> str: + versions = sorted({result.metadata.get("Evolve Version", "?") for result in results.values()}) + # Results measured on the same system share one line, so the note stays short. + systems: dict[str, list[str]] = {} + for label, result in results.items(): + driver = result.metadata.get("GPU Driver Version") + os_name = " ".join(filter(None, [result.metadata.get("OS Name"), result.metadata.get("OS Version")])) + details = ", ".join(filter(None, [result.gpu, f"driver {driver}" if driver else "", os_name])) + systems.setdefault(details, []).append(label) + lines = [f"Source: Evolve {', '.join(versions)} Β· first {WARM_UP_S:g} s skipped Β· mean per frame"] + if len(systems) == 1: + lines.append(f"All results: {next(iter(systems))}") + else: + lines += [f"{', '.join(labels)}: {details}" for details, labels in systems.items()] + return "
".join(lines) + + +def _show(fig, name: str, title: str, subtitle: str, height: int, note: str | None = ...) -> None: + context = _context() + note = context.note if note is ... else note + if note: + height += 20 * note.count("
") + charts.style(fig, title, subtitle, note, height) + st.plotly_chart( + fig, theme=None, key=name, config=charts.download_config(_slug(title), context.image_format, height) + ) + + +def _missing(present: set[str], what: str) -> bool: + """Says which results this page leaves out; True when none are left.""" + missing = [label for label in _context().labels if label not in present] + if len(missing) == len(_context().labels): + st.info(f"None of the loaded results contain {what}. See the Results page for what each file contains.") + return True + if missing: + st.caption(f"Not included, because their exports contain no {what}: {', '.join(missing)}.") + return False + + +def _comparison_columns(table: pd.DataFrame, labels: list[str], item_name: str) -> dict: + config = {"item": st.column_config.TextColumn(item_name)} + differences = tuple(f" vs {label}" for label in labels) + for column in table.columns: + if column in labels: + config[column] = st.column_config.NumberColumn(column, format="%.2f ms") + elif column.endswith(differences): + other, baseline = column.split(" vs ", 1) + config[column] = st.column_config.NumberColumn( + f"Ξ” {other}", format="%+.0f%%", help=f"Difference from {baseline}" + ) + return config + + +def _version() -> str: + version = VERSION_FILE.read_text().strip() if VERSION_FILE.exists() else "development version" + return f"Evolve Charts {version} Β· https://github.com/Traverse-Research/evolve-scripts" + + +def _slug(text: str) -> str: + return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") or "chart" + + +# Results --------------------------------------------------------------------------------------------- + + +def _results_page() -> None: + store = _store() + results = st.session_state["results"] + _title( + "Results", + "Load Evolve exports to compare them. Everything stays on this computer.", + ) + + columns = st.columns(CARDS_PER_ROW) + with columns[0].container(border=True, height="stretch"): + st.file_uploader( + "**Add Evolve exports**", + type=["zip", "csv", "json"], + accept_multiple_files=True, + key=f"uploader-{store['upload_key']}", + on_change=_on_upload, + help="The .zip from Export on Evolve's score screen, or the CSV and JSON files Evolve's command line " + "exports. Files in the `exports` folder next to this tool load automatically.", + ) + for error in store["errors"].values(): + st.error(error) + + if not results: + columns[1].markdown( + """ +**How to start** + +1. In Evolve, finish a benchmark and click **Export** on the score screen. +2. Repeat on every GPU or setting you want to compare, with the same benchmark. +3. Drop the files in the box on the left. +""" + ) + colors = charts.result_colors(list(results)) + for index, (label, result) in enumerate(results.items()): + slot = index + 1 + if slot % CARDS_PER_ROW == 0: + columns = st.columns(CARDS_PER_ROW) + with columns[slot % CARDS_PER_ROW].container(border=True, height="stretch"): + _result_card(result, label, colors[label]) + + if results: + problems = [] + for key, what in [("Evolve Version", "Evolve versions"), ("Rendering Backend", "graphics APIs")]: + values = {result.metadata.get(key, "unknown") for result in results.values()} + if len(values) > 1: + problems.append(f"different {what} ({', '.join(sorted(values))})") + if problems: + st.warning(f"These results use {' and '.join(problems)}. Render passes may differ; compare with care.") + for result in results.values(): + for warning in result.warnings: + st.info(warning) + + st.markdown("### How Evolve measures a GPU") + st.markdown(CONCEPTS, unsafe_allow_html=True) + st.write("") + st.caption(_version()) + + +def _result_card(result: Result, label: str, color: str) -> None: + store = _store() + st.markdown(f'
', unsafe_allow_html=True) + name = st.text_input( + "Name", + value=label, + key=f"label-{result.id}", + label_visibility="collapsed", + help="This name appears in every chart.", + ) + if name.strip() and name.strip() != label: + store["labels"][result.id] = name.strip() + st.rerun() + + meta = result.metadata + version = f"Evolve {meta['Evolve Version']}" if meta.get("Evolve Version") else "" + driver = f"Driver {meta['GPU Driver Version']}" if meta.get("GPU Driver Version") else "" + os_name = " ".join(filter(None, [meta.get("OS Name"), meta.get("OS Version")])) + lines = [ + f"{meta.get('GPU', 'Unknown GPU')}", + " Β· ".join(filter(None, [meta.get("Rendering Backend"), version])), + " Β· ".join(filter(None, [driver, os_name])), + ] + per_frame = result.frames if result.frames is not None else result.workloads + if per_frame is not None: + frames = per_frame[per_frame["seq_s"] >= WARM_UP_S] + duration = frames["seq_s"].max() - frames["seq_s"].min() if len(frames) else 0 + lines.append(f"{len(frames):,} frames Β· {duration // 60:.0f}:{duration % 60:02.0f} min") + contents = "
".join( + f"{'βœ“' if name in result.contents else 'βœ—'} {name}" for name in ["Scores", "Per frame", "Deep analysis"] + ) + body = "
".join(line for line in lines if line) + st.markdown(f'
{body}
{contents}
', unsafe_allow_html=True) + st.caption(result.filename) + if st.button("Remove", key=f"remove-{result.id}", icon=":material/close:", type="tertiary"): + store["removed"].add(result.id) + st.rerun() + + +# Scores ---------------------------------------------------------------------------------------------- + + +def _scores_page() -> None: + context = _context() + _title( + "Scores", + "Evolve scores each workload category: GPU work that logically belongs together, such as all ray tracing. " + "Higher is better; there is no total score.", + ) + table = stats.scores_table(context.results) + if _missing(set(table.columns), "scores"): + return + # Scores are calculated by Evolve over the whole run, so the warm-up note doesn't apply. + note = context.note and context.note.replace(f" Β· first {WARM_UP_S:g} s skipped Β· mean per frame", "") + _show( + charts.score_bars(table, context.colors), + "scores", + "Evolve scores", + "Higher is better", + charts.bars_height(len(table), len(table.columns)), + note=note, + ) + + display = table.copy() + baseline = context.baseline + if baseline in table: + for label in table.columns: + if label != baseline: + display[f"Ξ” {label}"] = 100 * (table[label] - table[baseline]) / table[baseline] + display.insert(0, "Workload category", [SCORE_WORKLOADS.get(name, "") for name in display.index]) + config = {label: st.column_config.NumberColumn(label, format="%.0f") for label in table.columns} + config |= { + column: st.column_config.NumberColumn(column, format="%+.0f%%", help=f"Difference from {baseline}") + for column in display.columns + if column.startswith("Ξ” ") + } + st.dataframe(display.rename_axis("Score"), column_config=config, placeholder="–") + st.caption("Open GPU time to see where each workload's GPU time goes over the run, and which passes it's made of.") + + +# GPU time -------------------------------------------------------------------------------------------- + + +def _gpu_time_page() -> None: + context = _context() + _title( + "GPU time", + "GPU time per frame of each workload category: the GPU work Evolve scores, such as all ray tracing. " + "Each workload category is made of passes.", + ) + summary = stats.summary(context.results, WARM_UP_S, "workload") + if _missing(set(summary["result"]), "per-frame data"): + return + present = [item for item in WORKLOAD_ORDER if summary.loc[summary["item"] == item, "mean_ms"].sum() > 0] + + overview, detail = st.tabs(["All workload categories", "One workload category"]) + with overview: + unit = st.segmented_control( + "Unit", + ["ms per frame", "% of GPU time"], + default="ms per frame", + required=True, + key="workload-unit", + label_visibility="collapsed", + ) + fig = charts.breakdown( + summary, + context.labels, + unit != "ms per frame", + WORKLOAD_ORDER, + charts.WORKLOAD_STYLES, + WORKLOAD_DESCRIPTIONS, + ) + _show( + fig, + "workload-breakdown", + "Where does the GPU time go?", + "Average GPU time per frame, by workload category", + max(240 + 70 * len(set(summary["result"])), 200 + 26 * len(fig.data)), + ) + table = stats.comparison_table(summary, context.labels, context.baseline) + table = table.set_index("item").reindex(present).drop(columns="workload").reset_index() + st.dataframe( + table, + hide_index=True, + column_config=_comparison_columns(table, context.labels, "Workload category"), + placeholder="–", + ) + + with detail: + item = st.selectbox( + "Workload category", + present, + index=present.index(DEFAULT_WORKLOAD) if DEFAULT_WORKLOAD in present else 0, + key="workload-item", + ) + st.caption(WORKLOAD_DESCRIPTIONS.get(item, "")) + sentence = stats.key_numbers(summary, item, context.baseline) + if sentence: + st.markdown(f'
{sentence}
', unsafe_allow_html=True) + + with_item = set(summary.loc[(summary["item"] == item) & (summary["mean_ms"] > 0), "result"]) + series = { + label: stats.timeline(result, WARM_UP_S, "workload", [item], SMOOTHING_S)[item] + for label, result in context.results.items() + # A run without this workload category gets no line rather than a flat zero. + if label in with_item + } + _show( + charts.lines(series, context.colors, "GPU time (ms)", "ms"), + "workload-timeline", + f"{item} over the run", + f"GPU time per frame, smoothed over {SMOOTHING_S:g} s", + 420, + ) + + # The passes this workload category is made of. + inside = stats.summary(context.results, WARM_UP_S, "pass", within=("workload", item)) + inside = inside[inside["mean_ms"] > 0] + if inside.empty: + st.caption("Load exports with deep analysis to see the passes this workload category is made of.") + return + ranked = inside.groupby("item")["mean_ms"].max().sort_values(ascending=False) + items = ranked.index[:TOP_PASSES].tolist() + shown = inside[inside["item"].isin(items)] + _show( + charts.bars(shown, context.labels, context.colors, items), + "workload-passes", + f"{item}: passes", + "Average GPU time per frame Β· whiskers: 5th to 95th percentile of frames", + # Size by how many results share a row: inline and pipeline runs have differently named passes. + charts.bars_height(len(items), shown.groupby("item")["result"].nunique().max()), + ) + if len(ranked) > TOP_PASSES: + st.caption(f"The {TOP_PASSES} largest of {len(ranked)} passes. The Passes page lists them all.") + left_out = stats.frame_totals(context.results, WARM_UP_S)["left_out_ms"] + if len(left_out): + st.caption( + f"Passes Evolve doesn't assign to any workload category (copies, clears, the user interface, …) " + f"are left out: {left_out.min():.1f}–{left_out.max():.1f} ms per frame." + ) + + +# Passes ---------------------------------------------------------------------------------------------- + + +def _passes_page() -> None: + context = _context() + _title("Passes", "Single blocks of GPU work, named as in Evolve. Tick passes to follow them over the run.") + summary = stats.summary(context.results, WARM_UP_S, "pass") + if _missing(set(summary["result"]), "deep analysis"): + return + labels = [label for label in context.labels if label in set(summary["result"])] + baseline = context.baseline if context.baseline in labels else labels[0] + if baseline != context.baseline: + st.caption(f"Compared against {baseline}: {context.baseline} has no deep analysis.") + table = stats.comparison_table(summary, labels, baseline) + # Copies, clears and other support work still count towards their workload category, but aren't listed. + utility = table["item"].map(is_utility) + table = table[~utility].reset_index(drop=True) + others = [label for label in labels if label != baseline] + + search, sort = st.columns([3, 2], vertical_alignment="bottom") + query = search.text_input( + "Search", placeholder="Search passes or categories, e.g. shadow", label_visibility="collapsed" + ) + sort_options = ["Biggest difference", "Most GPU time"] if others else ["Most GPU time"] + order = sort.segmented_control( + "Sort by", sort_options, default=sort_options[0], required=True, key="passes-sort", label_visibility="collapsed" + ) + if query: + text = table["item"] + " " + table["workload"] + table = table[text.str.contains(query, case=False, regex=False)] + if order == "Biggest difference": + # Rank by absolute time: a 300% change on a 0.01 ms pass matters less than 20% on a 2 ms pass. + rank = table[others].sub(table[baseline], axis=0).abs().max(axis=1) + else: + rank = table[labels].max(axis=1) + table = table.assign(_rank=rank).sort_values("_rank", ascending=False).drop(columns="_rank").reset_index(drop=True) + + event = st.dataframe( + table, + hide_index=True, + height=360, + column_config=_comparison_columns(table, labels, "Pass") + | { + "workload": st.column_config.TextColumn("Workload category"), + }, + placeholder="–", + on_select="rerun", + selection_mode="multi-row", + selection_default={"selection": {"rows": [0]}} if len(table) else None, + key=f"passes-table-{query}-{order}-{baseline}", + ) + picked = table.iloc[event.selection.rows]["item"].tolist() if len(table) else [] + if not picked: + st.info("Tick at least one pass in the table.") + return + + combine = len(picked) > 1 and st.toggle("Add the selected passes together", key="passes-combine") + to_draw = [(" + ".join(picked), picked)] if combine else [(item, [item]) for item in picked] + for index, (name, items) in enumerate(to_draw): + series = { + label: stats.timeline(result, WARM_UP_S, "pass", items, SMOOTHING_S).sum(axis=1) + for label, result in context.results.items() + if result.passes is not None and result.passes["pass"].isin(items).any() + } + _show( + charts.lines(series, context.colors, "GPU time (ms)", "ms"), + f"passes-timeline-{index}", + name if len(name) < 80 else f"{len(items)} passes added together", + f"GPU time per frame over the run, smoothed over {SMOOTHING_S:g} s", + 440, + ) + + +# GPU sensors ----------------------------------------------------------------------------------------- + + +def _sensors_page() -> None: + context = _context() + _title("GPU sensors", "Clocks, power and temperature during the run. Falling clocks can mean throttling.") + timelines = { + label: stats.telemetry_timeline(result, WARM_UP_S, SMOOTHING_S) + for label, result in context.results.items() + if result.frames is not None + } + if _missing({label for label, timeline in timelines.items() if len(timeline.columns)}, "GPU sensor data"): + return + available = [column for column in stats.TELEMETRY if any(column in timeline for timeline in timelines.values())] + names = {column: f"{stats.TELEMETRY[column][0]} ({stats.TELEMETRY[column][1]})" for column in available} + column = st.pills( + "Sensor", + available, + default=available[0], + required=True, + format_func=names.get, + key="sensor", + label_visibility="collapsed", + ) + name, unit = stats.TELEMETRY[column] + series = {label: timeline[column] for label, timeline in timelines.items() if column in timeline} + _show( + charts.lines(series, context.colors, f"{name} ({unit})", unit, from_zero=False), + f"telemetry-{column}", + f"{name} during the run", + f"Smoothed over {SMOOTHING_S:g} s", + 460, + ) + with st.expander("All sensors: averages and maximums"): + summary = stats.telemetry_summary(context.results, WARM_UP_S) + summary = summary.reindex([label for label in context.labels if label in timelines]) + st.dataframe(summary.T.style.format("{:.1f}", na_rep="–")) + + +# Export ---------------------------------------------------------------------------------------------- + + +def _export_page() -> None: + results = _context().results + _title( + "Export", + "CSV files for Excel, Google Sheets, Datawrapper or Flourish. Numbers use a dot as decimal separator; " + "in Dutch or German Excel import them with Data β€Ί From Text/CSV.", + ) + # Each table is only built when its button is clicked; the per-frame one is tens of megabytes. + downloads = [ + ( + "Scores", + "One row per score, one column per result.", + lambda: stats.scores_table(results).rename_axis("score").reset_index(), + "scores.csv", + ), + ( + "Workload categories", + "Average ms per frame per workload category, one column per result.", + lambda: stats.wide_for_spreadsheets(stats.summary(results, WARM_UP_S, "workload")).drop(columns="workload"), + "workload_categories.csv", + ), + ( + "Passes", + "Mean, median and percentiles for every pass and result.", + lambda: stats.summary(results, WARM_UP_S, "pass").rename(columns={"item": "pass"}), + "passes.csv", + ), + ( + "Every frame", + "GPU time of every pass in every frame. Large.", + lambda: _per_frame_long(results, WARM_UP_S), + "passes_per_frame.csv", + ), + ] + columns = st.columns(3) + for index, (title, description, table, filename) in enumerate(downloads): + with columns[index % 3].container(border=True, height="stretch"): + st.markdown(f"**{title}**") + st.caption(description) + st.download_button( + "Download CSV", + lambda table=table: table().to_csv(index=False, float_format="%.4f").encode("utf-8-sig"), + file_name=filename, + mime="text/csv", + key=f"download-{index}", + on_click="ignore", + icon=":material/download:", + type="primary", + ) + + left_out = pd.DataFrame( + {label: _left_out_per_frame(result) for label, result in results.items() if result.left_out} + ) + if not left_out.empty: + with st.expander("Passes left out because Evolve doesn't assign them to a workload category"): + st.dataframe( + left_out.sort_values(left_out.columns[0], ascending=False).style.format("{:.3f} ms", na_rep="–") + ) + + +def _left_out_per_frame(result: Result) -> pd.Series: + # Only totals over the whole run are kept for left-out passes, so this average includes the warm-up. + frames = len(result.frames) + return pd.Series({name: total / frames for name, total in result.left_out.items()}, dtype=float) + + +def _per_frame_long(results: dict[str, Result], skip_s: float) -> pd.DataFrame: + frames = [] + for label, result in results.items(): + if result.passes is None: + continue + passes = result.passes[result.passes["seq_s"] >= skip_s] + frames.append( + passes.assign(result=label, workload=passes["pass"].map(lambda scope: ", ".join(workloads(scope))))[ + ["result", "loop", "frame", "seq_s", "workload", "pass", "ms"] + ] + ) + if not frames: + return pd.DataFrame(columns=["result", "loop", "frame", "benchmark_time_s", "workload", "pass", "ms"]) + return pd.concat(frames).rename(columns={"seq_s": "benchmark_time_s"}) diff --git a/exports/README.txt b/exports/README.txt new file mode 100644 index 0000000..350b906 --- /dev/null +++ b/exports/README.txt @@ -0,0 +1,2 @@ +Put Evolve export .zip files in this folder and Evolve Charts loads them automatically. +You can also drag them into the upload box in the tool instead. diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b2b3d85..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -flatten-json>=0.1.14 -matplotlib>=3.10.0 -numpy>=1.26.4 -pandas>=2.2.3 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..f11cf63 --- /dev/null +++ b/ruff.toml @@ -0,0 +1 @@ +line-length = 120 diff --git a/scripts/compare_deep_analysis.py b/scripts/compare_deep_analysis.py deleted file mode 100644 index 6345c30..0000000 --- a/scripts/compare_deep_analysis.py +++ /dev/null @@ -1,173 +0,0 @@ -import pandas as pd -import json -from collections import defaultdict -import numpy as np -import argparse -import sys - -from flatten_json import flatten - -""" -Aggregates the gpu render pass data and metrics over all loop iteration to their mean over the loop iterations -""" - - -def aggregate_loops_passes(json): - results_per_frame = [] - num_loops = len(json) - for loop_results in json: - for frame_index, frame_results in enumerate(loop_results["per_frame_results"]): - if frame_index >= len(results_per_frame): - results_per_frame.append(defaultdict(int)) - for command_buffer_timings in frame_results[ - "command_buffer_timings" - ].values(): - for scope_name, scope_timings in command_buffer_timings[ - "scope_timings" - ].items(): - for scope_timing in scope_timings: - results_per_frame[frame_index][scope_name] += ( - scope_timing["end"] - scope_timing["start"] - ) / num_loops - for metric_name, metric in frame_results["metrics"].items(): - # TODO: Flatten this in rust to fan_speed_rpm - if metric_name == "fan_speed": - results_per_frame[frame_index]["fan_speed_rpm"] += ( - metric["Rpm"] / num_loops - ) - # Filter out unavailable data and the timestamp - elif metric is not None and metric_name != "timestamp": - results_per_frame[frame_index][metric_name] += metric / num_loops - # TODO: Aggregate CPU timings - return pd.DataFrame([flatten(x) for x in results_per_frame]) - - -def metric_names(): - return [ - "edge_temperature_in_c", - "hotspot_temperature_in_c", - "usage_percentage", - "fan_speed_rpm", - "clock_speed_in_mhz", - "vram_clock_speed_in_mhz", - "power_usage_in_w", - "board_power_usage_in_w", - "voltage_in_mv", - "vram_usage_in_mb", - ] - - -""" -Outputs the 20 passes with the highest difference in mean between the two -deep analysis inputs -""" - - -def output_top_passes(input_1, input_2): - combined_data = pd.concat([input_1.mean(), input_2.mean()], axis=1) - combined_data.columns = ["Input 1", "Input 2"] - combined_data.index.names = ["Pass Name"] - combined_data["pct_diff"] = ( - combined_data["Input 1"] / combined_data["Input 2"] - ).abs().drop(metric_names()) * 100 - combined_data = ( - combined_data.sort_values(by="pct_diff", ascending=False) - .iloc[:20] - .round() - .dropna() - .astype(int) - ) - return combined_data - - -""" -Output the 20 passes with the highest difference in standard deviation -between the two deep deep analysis input DataFrames -""" - - -def output_top_stdev(input_1, input_2): - combined_data = pd.concat([input_1.std(), input_2.std()], axis=1) - combined_data.columns = ["Input 1", "Input 2"] - combined_data.index.names = ["Pass Name"] - # In case there's a mismatch in frames etc. - combined_data.replace(0, np.nan, inplace=True) - - # Convert to percentages, since tools like google sheets won't correctly interpret decimal results - # depending on the set language - combined_data["pct_diff"] = ( - combined_data["Input 1"] / combined_data["Input 2"] - ).abs().drop(metric_names()) * 100 - combined_data = ( - combined_data.sort_values(by="pct_diff", ascending=False) - .iloc[:20] - .round() - .dropna() - .astype(int) - ) - combined_data = combined_data.drop("pct_diff", axis=1) - return combined_data - - -def main(): - parser = argparse.ArgumentParser( - prog="Evolve Deep Analysis Comparison", - description="Compares two files produced by Evolve's deep analysis export", - ) - parser.add_argument( - "deep_analysis_file", - help="The deep analysis output files to use for the comparative analysis", - nargs=2, - ) - parser.add_argument( - "--pass_mean_comparison", - help="The file to write the top 20 passes with the highest difference in mean execution time per pass to the specified path as csv. Useful for comparing the execution time of passes between different input files.", - metavar="csv_output_filename" - ) - parser.add_argument( - "--pass_stdev_comparison", - help="The file to write the top 20 passes with the highest difference of the standard deviation over execution time to the specified path as csv. Useful for comparing the variance in execution times of passes between different input files.", - metavar="csv_output_filename" - ) - try: - args = parser.parse_args() - except argparse.ArgumentError as e: - # This works around some inconsistent behaviour with argparse on different Python versions. - # < 3.11 seems to always throw an exception, regardless of `exit_on_error` - # 3.11 seems to never throw an exception, regardless of `exit_on_error` (it will exit itself) - # >= 3.13 (?) seems to respect `exit_on_error` - # Avoid an exception being thrown when we fail to parse the args, so that we can manually print - # the usage message as well - print(f"Error: {e}\n", file=sys.stderr) - parser.print_usage() - exit(2) - - if args.pass_mean_comparison is None and args.pass_stdev_comparison is None: - print("Error: No analysis option specified, specify at least one\n") - parser.print_usage() - exit(1) - elif args.pass_mean_comparison == args.pass_stdev_comparison: - print( - "Error: Identical file outputs specified for analysis. Exiting as one will overwrite the other" - ) - exit(1) - - output = {} - - for path in args.deep_analysis_file: - with open(path, "r") as json_file: - json_data = aggregate_loops_passes(json.load(json_file)) - # TODO: Use something else than input file path as naming scheme? - output[path] = json_data - - # argparse already asserts that the number of inputs is two - first, second = output.values() - - if args.pass_mean_comparison is not None: - output_top_passes(first, second).to_csv(args.pass_mean_comparison) - if args.pass_stdev_comparison is not None: - output_top_stdev(first, second).to_csv(args.pass_stdev_comparison) - - -if __name__ == "__main__": - main() diff --git a/start-evolve-charts.sh b/start-evolve-charts.sh new file mode 100755 index 0000000..c098f48 --- /dev/null +++ b/start-evolve-charts.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Starts Evolve Charts on macOS or Linux. A page opens in your browser. +set -e +cd "$(dirname "$0")" +export PATH="$HOME/.local/bin:$PATH" + +if ! command -v uv >/dev/null 2>&1; then + echo "Evolve Charts needs \"uv\" to download Python and the libraries it uses." + echo "It will now be installed for your user account from https://astral.sh/uv" + curl -LsSf https://astral.sh/uv/install.sh | sh +fi + +echo "Starting Evolve Charts. The first start downloads Python and the libraries it uses" +echo "(about 150 MB) and can take a few minutes; later starts are quick." +echo "Keep this window open while you use the tool; press Ctrl+C to stop." +exec uv run evolve-charts.py diff --git a/static/Spartan-Bold.ttf b/static/Spartan-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9c4a695ff965f0211830e986548875956d147a4b GIT binary patch literal 63120 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 literal 0 HcmV?d00001 diff --git a/static/Spartan-Regular.ttf b/static/Spartan-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0b7f73c893e7b5bd10db9d53923353fbe4e20897 GIT binary patch literal 64308 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 literal 0 HcmV?d00001 diff --git a/static/evolve-icon.png b/static/evolve-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bdd240ff9b804b9eecc7c8ea64ceee3d7c545fb0 GIT binary patch literal 7067 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 literal 0 HcmV?d00001 diff --git a/static/evolve-logo.png b/static/evolve-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..241189df88d2f4088ca1d819411b4108ca7f09f5 GIT binary patch literal 23403 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 dict: + return {"start": int(start_ms * 1e6), "end": int(end_ms * 1e6)} + + +def frame(seq_s: float, buffers: list[dict[str, list[dict]]], metrics: dict | None = None) -> dict: + return { + "sequence_time_ns": int(seq_s * 1e9), + "command_buffer_timings": { + f"Command Buffer {index}": {"cpu_submit_time": 0, "gpu_submit_time": 0, "scope_timings": scopes} + for index, scopes in enumerate(buffers) + }, + "cpu_timings": {"DriverApiCall": [1000]}, + "metrics": metrics, + } + + +def simple_frames(shadow_ms: float, count: int = 20) -> list[dict]: + """Frames 0.5 s apart; frame 0 is a slow warm-up frame. + + Per frame: shadow ray tracing (Raytracing workload), 2 Γ— 0.25 ms of shadow filtering (Compute workload) + and 0.1 ms of work Evolve doesn't assign to a workload category. + """ + frames = [] + for index in range(count): + base = index * 100.0 + extra = 50.0 if index == 0 else 0.0 + frames.append( + frame( + index * 0.5, + [ + { + "shadow-raytrace-inline": [scope(base, base + shadow_ms + extra)], + "shadow_filter_pass_0": [scope(base + 60, base + 60.25), scope(base + 61, base + 61.25)], + "made-up-pass": [scope(base + 62, base + 62.1)], + } + ], + {"clock_speed_in_mhz": 2500 + index, "board_power_usage_in_w": 200.0, "fan_speed": {"Rpm": 1500}}, + ) + ) + return frames + + +def combined_csv(gpu: str, shadow_ms: float, count: int = 20, scores: str = "4000,5000,6000,0") -> str: + """`evolve_results.csv`: system information, scores and per-frame workload times on every row.""" + rows = [f"{METADATA_HEADER},{SCORES_HEADER},{PER_FRAME_HEADER}"] + for index in range(count): + ray_tracing = int((shadow_ms + (50 if index == 0 else 0)) * 1e6) + rows.append( + f"15.10,host,CPU X,{gpu},Windows,11,1.2.3,Dx12,{scores}," + f"1,{index},{int(index * 0.5e9)},{ray_tracing},,{int(0.5e6)},200.0,{2500 + index}" + ) + return "\n".join(rows) + "\n" + + +def make_export( + loops: list[list[dict]] | None, + gpu: str = "Test GPU", + truncated: bool = False, + results_csv: str | None = None, +) -> bytes: + """An Evolve Export zip; `loops=None` leaves out deep analysis like exports without Professional.""" + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w") as archive: + if loops is not None: + text = json.dumps( + [{"loop_index": index + 1, "per_frame_results": frames} for index, frames in enumerate(loops)] + ) + archive.writestr("deep_analysis.json", text[:-1] if truncated else text) + shadow_ms = 2.0 if "1" in gpu else 1.0 + archive.writestr("evolve_results.csv", results_csv if results_csv is not None else combined_csv(gpu, shadow_ms)) + return buffer.getvalue() + + +@pytest.fixture +def two_gpus(tmp_path: Path) -> Path: + (tmp_path / "gen1.zip").write_bytes(make_export([simple_frames(2.0)], gpu="Gen 1 GPU")) + (tmp_path / "gen2.zip").write_bytes(make_export([simple_frames(1.0)], gpu="Gen 2 GPU")) + return tmp_path diff --git a/tests/test_loader.py b/tests/test_loader.py new file mode 100644 index 0000000..04dedf0 --- /dev/null +++ b/tests/test_loader.py @@ -0,0 +1,137 @@ +import io +import zipfile + +import pytest +from conftest import PER_FRAME_HEADER, combined_csv, frame, make_export, scope, simple_frames + +from evolve_charts.categories import workloads +from evolve_charts.loader import ExportError, load_export + + +def test_full_export(): + result = load_export(make_export([simple_frames(2.0)], gpu="Gen 1 GPU"), "gen1.zip") + assert result.gpu == "Gen 1 GPU" + assert result.metadata["Evolve Version"] == "15.10" + assert result.metadata["Rendering Backend"] == "Dx12" + # A score of 0 means Evolve didn't calculate it for this benchmark. + assert result.scores == {"Ray Tracing": 4000, "Rasterization": 5000, "Compute": 6000} + assert result.contents == ["Scores", "Per frame", "Deep analysis"] + assert set(result.passes["pass"]) == {"shadow-raytrace-inline", "shadow_filter_pass_0"} + assert result.warnings == [] + + +def test_per_frame_workloads_come_from_the_csv(): + result = load_export(make_export([simple_frames(2.0)], gpu="Gen 1 GPU"), "gen1.zip") + frame_3 = result.workloads.set_index("frame").loc[3] + assert frame_3["Raytracing"] == pytest.approx(2.0) + assert frame_3["Compute"] == pytest.approx(0.5) + assert "Rasterization" not in result.workloads # an all-empty column + + +def test_export_without_deep_analysis(): + result = load_export(make_export(None, gpu="Gen 1 GPU"), "gen1.zip") + assert result.contents == ["Scores", "Per frame"] + assert result.passes is None + assert result.frames["gpu_clock_mhz"].iloc[2] == 2502 + assert result.frames["board_power_w"].iloc[2] == 200.0 + + +def test_standalone_csv_exports(): + scores = load_export(b"Loop,Ray Tracing,Compute\n1,4000,6000\n2,4200,0\n", "scores.csv") + assert scores.scores == {"Ray Tracing": 4100, "Compute": 6000} + assert scores.contents == ["Scores"] + assert any("system information" in warning for warning in scores.warnings) + + per_frame = load_export(f"{PER_FRAME_HEADER}\n1,0,0,1000000,,500000,150,2600\n".encode(), "frametimes.csv") + assert per_frame.contents == ["Per frame"] + assert per_frame.workloads["Raytracing"].item() == pytest.approx(1.0) + + combined = load_export(combined_csv("Gen 2 GPU", 1.0).encode(), "evolve_results.csv") + assert combined.gpu == "Gen 2 GPU" + assert combined.contents == ["Scores", "Per frame"] + + +def test_standalone_deep_analysis_derives_workloads(): + data = zipfile.ZipFile(io.BytesIO(make_export([simple_frames(2.0)]))).read("deep_analysis.json") + result = load_export(data, "deep_analysis.json") + assert result.contents == ["Per frame", "Deep analysis"] + frame_3 = result.workloads.set_index("frame").loc[3] + assert frame_3["Raytracing"] == pytest.approx(2.0) + assert frame_3["Compute"] == pytest.approx(0.5) + + +def test_uncategorized_scopes_are_left_out_but_counted(): + result = load_export(make_export([simple_frames(2.0)]), "x.zip") + assert result.left_out == {"made-up-pass": pytest.approx(0.1 * 20)} + assert result.frames["left_out_ms"].tolist() == pytest.approx([0.1] * 20, abs=1e-5) + # The GPU frame span still covers all GPU work, including what's left out. + assert result.frames["gpu_span_ms"].iloc[3] == pytest.approx(62.1) + + +def test_repeated_scopes_in_a_frame_are_summed(): + passes = load_export(make_export([simple_frames(2.0)]), "x.zip").passes + filter_ms = passes[(passes["pass"] == "shadow_filter_pass_0") & (passes["frame"] == 3)]["ms"] + assert filter_ms.item() == pytest.approx(0.5) + + +def test_nested_scopes_count_once_in_totals(): + nested = frame(0.0, [{"Build GBuffers": [scope(0, 10)], "shadow_prepare": [scope(2, 4)]}]) + result = load_export(make_export([[nested]]), "x.zip") + passes = result.passes.set_index("pass") + assert passes.at["shadow_prepare", "ms"] == pytest.approx(2.0) + assert passes.at["shadow_prepare", "top_ms"] == 0.0 + assert result.frames["gpu_sum_ms"].item() == pytest.approx(10.0) + assert any("nested" in warning for warning in result.warnings) + + +def test_frame_span_and_overlap_across_command_buffers(): + overlapping = frame(0.0, [{"Build GBuffers": [scope(0, 6)]}, {"shadow_prepare": [scope(4, 10)]}]) + result = load_export(make_export([[overlapping]]), "x.zip") + assert result.frames["gpu_sum_ms"].item() == pytest.approx(12.0) + assert result.frames["gpu_span_ms"].item() == pytest.approx(10.0) + + +def test_metrics_null_and_fan_units(): + frames = [ + frame(0.0, [{"Build GBuffers": [scope(0, 1)]}], None), + frame(1.0, [{"Build GBuffers": [scope(0, 1)]}], {"fan_speed": {"Percent": 40}, "edge_temperature_in_c": 60.0}), + frame(2.0, [{"Build GBuffers": [scope(0, 1)]}], {"fan_speed": {"Rpm": 1200}, "clock_speed_in_mhz": None}), + ] + result = load_export(make_export([frames]), "x.zip") + assert result.frames["fan_pct"].tolist()[1] == 40 + assert result.frames["fan_rpm"].tolist()[2] == 1200 + assert result.frames["gpu_clock_mhz"].isna().all() + + +def test_interrupted_multi_loop_run_is_recovered(): + result = load_export(make_export([simple_frames(2.0, 5), simple_frames(2.0, 5)], truncated=True), "x.zip") + assert result.frames["loop"].nunique() == 2 + assert any("interrupted" in warning for warning in result.warnings) + + +def test_unreadable_inputs(): + with pytest.raises(ExportError, match="not a zip"): + load_export(b"{}", "broken.zip") + with pytest.raises(ExportError, match="isn't an Evolve"): + load_export(b"a,b\n1,2\n", "other.csv") + with pytest.raises(ExportError, match="use an Evolve export"): + load_export(b"", "picture.png") + + +def test_categories(): + assert workloads("shadow_filter_pass_1") == ["Compute"] + assert workloads("DLSS evaluate") == ["Upscaling"] + assert workloads("egui_render") == [] + assert workloads("not-in-evolve") == [] + assert workloads("shadow-raytrace-inline") == ["Raytracing"] + assert workloads("vol-ris-initial-sampling") == ["Raytracing", "Compute"] + + +def test_utility_passes(): + from evolve_charts.categories import is_utility + + assert is_utility("Copy buffer to buffer") + assert is_utility("egui_render") + # Uncategorized by Evolve's pass bucketing, but the work of a workload category of their own. + assert not is_utility("DLSS evaluate") + assert not is_utility("shadow_filter_pass_0") diff --git a/tests/test_stats.py b/tests/test_stats.py new file mode 100644 index 0000000..2c8722b --- /dev/null +++ b/tests/test_stats.py @@ -0,0 +1,97 @@ +import pytest +from conftest import make_export, simple_frames + +from evolve_charts import stats +from evolve_charts.loader import load_export + + +@pytest.fixture +def results(): + return { + "Gen 1": load_export(make_export([simple_frames(2.0)], gpu="Gen 1 GPU"), "gen1.zip"), + "Gen 2": load_export(make_export([simple_frames(1.0)], gpu="Gen 2 GPU"), "gen2.zip"), + } + + +def summary_of(results, level, within=None, skip_s=1.0): + return stats.summary(results, skip_s, level, within).set_index(["result", "item"]) + + +def test_warm_up_is_skipped(results): + assert summary_of(results, "pass", skip_s=0.0).at[("Gen 1", "shadow-raytrace-inline"), "mean_ms"] > 2.0 + assert summary_of(results, "pass").at[("Gen 1", "shadow-raytrace-inline"), "mean_ms"] == pytest.approx(2.0) + + +def test_frame_totals(results): + totals = stats.frame_totals(results, 1.0).set_index("result") + assert totals.at["Gen 1", "gpu_sum_ms"] == pytest.approx(2.5) + assert totals.at["Gen 1", "left_out_ms"] == pytest.approx(0.1) + + +def test_workload_categories(results): + workloads = summary_of(results, "workload") + assert workloads.at[("Gen 2", "Raytracing"), "mean_ms"] == pytest.approx(1.0) + assert workloads.at[("Gen 2", "Compute"), "mean_ms"] == pytest.approx(0.5) + rows = workloads.loc["Gen 1"] + assert rows["share_pct"].sum() == pytest.approx(100.0) + + +def test_passes_of_a_workload_category(results): + passes = summary_of(results, "pass", ("workload", "Compute")) + assert passes.index.get_level_values("item").unique().tolist() == ["shadow_filter_pass_0"] + assert passes.at[("Gen 1", "shadow_filter_pass_0"), "workload"] == "Compute" + + +def test_results_without_deep_analysis_are_left_out_of_pass_views(): + results = { + "Full": load_export(make_export([simple_frames(2.0)], gpu="Gen 1 GPU"), "a.zip"), + "Scores only": load_export(make_export(None, gpu="Gen 2 GPU"), "b.zip"), + } + assert set(stats.summary(results, 1.0, "pass")["result"]) == {"Full"} + assert set(stats.summary(results, 1.0, "workload")["result"]) == {"Full", "Scores only"} + + +def test_passes_missing_in_some_frames_count_as_zero(results): + result = results["Gen 1"] + even = (result.passes["pass"] == "shadow_filter_pass_0") & (result.passes["frame"] % 2 == 0) + result.passes = result.passes[~even] + row = stats.summary({"Gen 1": result}, 1.0, "pass").set_index("item").loc["shadow_filter_pass_0"] + assert row["ran_in_pct_of_frames"] == pytest.approx(50.0, abs=5.0) + assert row["mean_ms"] == pytest.approx(0.5 * row["ran_in_pct_of_frames"] / 100) + + +def test_scores_table(results): + table = stats.scores_table(results) + assert table.index.tolist() == ["Ray Tracing", "Rasterization", "Compute"] + assert table.at["Compute", "Gen 2"] == 6000 + + +def test_comparison_table_and_key_numbers(results): + summary = stats.summary(results, 1.0, "pass") + table = stats.comparison_table(summary, ["Gen 1", "Gen 2"], "Gen 1").set_index("item") + assert table.at["shadow-raytrace-inline", "Gen 2"] == pytest.approx(1.0) + assert table.at["shadow-raytrace-inline", "Gen 2 vs Gen 1"] == pytest.approx(-50.0) + workloads = stats.summary(results, 1.0, "workload") + assert stats.key_numbers(workloads, "Raytracing", "Gen 1") == ( + "Raytracing: 2.00 ms per frame on Gen 1, 1.00 ms on Gen 2 (50% less)." + ) + + +def test_timeline_averages_loops_and_smooths(): + result = load_export(make_export([simple_frames(2.0), simple_frames(4.0)]), "x.zip") + raw = stats.timeline(result, 1.0, "pass", ["shadow-raytrace-inline"], 0.0) + assert raw["shadow-raytrace-inline"].tolist() == pytest.approx([3.0] * len(raw)) + smooth = stats.timeline(result, 1.0, "pass", ["shadow-raytrace-inline"], 2.0) + assert len(smooth) == len(raw) + assert len(stats.timeline(result, 1.0, "workload", ["Raytracing"], 0.0)) > 0 + + +def test_telemetry(results): + summary = stats.telemetry_summary(results, 1.0) + assert summary.at["Gen 1", "GPU power avg (W)"] == pytest.approx(200.0) + assert "Fan speed avg (RPM)" in summary.columns + + +def test_wide_for_spreadsheets(results): + wide = stats.wide_for_spreadsheets(stats.summary(results, 1.0, "pass")) + assert list(wide.columns) == ["workload", "item", "Gen 1", "Gen 2"] diff --git a/tests/test_ui.py b/tests/test_ui.py new file mode 100644 index 0000000..6a93aca --- /dev/null +++ b/tests/test_ui.py @@ -0,0 +1,70 @@ +from pathlib import Path + +from conftest import make_export +from streamlit.testing.v1 import AppTest + +ENTRY = str(Path(__file__).resolve().parent.parent / "evolve-charts.py") +PAGES = ["_scores_page", "_gpu_time_page", "_passes_page", "_sensors_page", "_export_page"] + +# Analysis pages are functions given to st.navigation, which AppTest can't switch to, so a small script renders +# one the way main() does: the shared context bar first, then the page. +PAGE_SCRIPT = """ +import streamlit as st +from evolve_charts import ui + +results = ui._loaded_results() +st.session_state["results"] = results +st.session_state["context"] = ui._context_bar(results) +getattr(ui, "{page}")() +""" + + +def run(monkeypatch, folder) -> AppTest: + monkeypatch.setenv("EVOLVE_CHARTS_EXPORTS", str(folder)) + return AppTest.from_file(ENTRY, default_timeout=60).run() + + +def page(monkeypatch, folder, name: str) -> AppTest: + monkeypatch.setenv("EVOLVE_CHARTS_EXPORTS", str(folder)) + app = AppTest.from_string(PAGE_SCRIPT.format(page=name), default_timeout=60).run() + assert not app.exception, name + return app + + +def visit_every_page(monkeypatch, folder) -> None: + for name in PAGES: + page(monkeypatch, folder, name) + + +def test_results_page_without_results(tmp_path, monkeypatch): + app = run(monkeypatch, tmp_path) + assert not app.exception + assert any("How to start" in markdown.value for markdown in app.markdown) + + +def test_every_page_renders_with_two_results(two_gpus, monkeypatch): + app = run(monkeypatch, two_gpus) + assert not app.exception + assert [box.value for box in app.text_input] == ["Gen 1 GPU", "Gen 2 GPU"] + visit_every_page(monkeypatch, two_gpus) + + app = page(monkeypatch, two_gpus, "_gpu_time_page") + assert any("Raytracing: 2.00 ms per frame on Gen 1 GPU" in markdown.value for markdown in app.markdown) + app.selectbox(key="workload-item").set_value("Compute").run() + assert not app.exception + assert any("Compute: 0.50 ms per frame on Gen 1 GPU" in markdown.value for markdown in app.markdown) + + +def test_results_without_deep_analysis(tmp_path, monkeypatch): + (tmp_path / "scores_only.zip").write_bytes(make_export(None, gpu="Gen 1 GPU")) + (tmp_path / "scores.csv").write_text("Loop,Ray Tracing,Compute\n1,4000,6000\n") + app = run(monkeypatch, tmp_path) + assert not app.exception + visit_every_page(monkeypatch, tmp_path) + + +def test_bad_file_shows_error(tmp_path, monkeypatch): + (tmp_path / "broken.zip").write_bytes(b"not a zip") + app = run(monkeypatch, tmp_path) + assert not app.exception + assert "not a zip" in app.error[0].value diff --git a/tools/package.py b/tools/package.py new file mode 100644 index 0000000..1ba8e37 --- /dev/null +++ b/tools/package.py @@ -0,0 +1,78 @@ +"""Build the Evolve Charts download: a zip with only what's needed to run the tool. + +Usage (from the repository root): + + python tools/package.py [version] + +Writes dist/Evolve-Charts.zip with one "Evolve Charts" folder inside. The GitHub release workflow runs +this and attaches the zip to the release. +""" + +import stat +import sys +import zipfile +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +FOLDER = "Evolve Charts" +OUTPUT = REPO_ROOT / "dist/Evolve-Charts.zip" + +FILES = [ + "Start Evolve Charts.bat", + "Start Evolve Charts.command", + "start-evolve-charts.sh", + "evolve-charts.py", + "evolve-charts.py.lock", + ".streamlit/config.toml", + "exports/README.txt", +] +FOLDERS = ["evolve_charts", "static"] +EXECUTABLE = {"Start Evolve Charts.command", "start-evolve-charts.sh"} + +HOW_TO_START = """Evolve Charts {version} +https://github.com/Traverse-Research/evolve-scripts + +Compare Evolve benchmark results in your web browser. Nothing is uploaded anywhere. + +Windows: double-click "Start Evolve Charts". + If Windows asks whether to run it, choose "More info" and then "Run anyway". +macOS: right-click "Start Evolve Charts.command" and choose "Open" (only needed the first time). +Linux: run ./start-evolve-charts.sh in a terminal. + +The first start downloads Python and the libraries the tool uses (about 150 MB) and needs an +internet connection. Keep the window that opens running while you use the tool. + +To load results automatically, put Evolve export files in the "exports" folder. +""" + + +def main() -> None: + version = sys.argv[1] if len(sys.argv) > 1 else "development version" + paths = [REPO_ROOT / name for name in FILES] + for folder in FOLDERS: + paths += sorted( + path for path in (REPO_ROOT / folder).rglob("*") if path.is_file() and "__pycache__" not in path.parts + ) + + OUTPUT.parent.mkdir(exist_ok=True) + with zipfile.ZipFile(OUTPUT, "w", zipfile.ZIP_DEFLATED) as archive: + for path in paths: + relative = path.relative_to(REPO_ROOT).as_posix() + info = zipfile.ZipInfo.from_file(path, f"{FOLDER}/{relative}") + mode = 0o755 if relative in EXECUTABLE else 0o644 + info.external_attr = (stat.S_IFREG | mode) << 16 + info.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(info, path.read_bytes()) + for name, text in [ + ("HOW TO START.txt", HOW_TO_START.format(version=version).replace("\n", "\r\n")), + ("VERSION", version), + ]: + info = zipfile.ZipInfo(f"{FOLDER}/{name}") + info.external_attr = (stat.S_IFREG | 0o644) << 16 + info.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(info, text) + print(f"Wrote {OUTPUT.relative_to(REPO_ROOT)} ({len(paths) + 2} files, version {version})") + + +if __name__ == "__main__": + main() diff --git a/tools/sync_categories.py b/tools/sync_categories.py new file mode 100644 index 0000000..064473c --- /dev/null +++ b/tools/sync_categories.py @@ -0,0 +1,85 @@ +"""Regenerate evolve_charts/scope_catalog.py from Evolve's frame bucketing source. + +Usage (from the repository root): + + python tools/sync_categories.py [path/to/evolve/crates/evolve-frame-bucketing/src/lib.rs] + +The Rust source is the single source of truth for which GPU scope (render pass) belongs to which +`WorkloadCategory`, and for the names Evolve shows for them. It also lists the scopes Evolve's pass +bucketing leaves uncategorized (copies, clears, the user interface, ...), which the tool doesn't list as +passes. +""" + +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +DEFAULT_SOURCE = REPO_ROOT.parent / "evolve/crates/evolve-frame-bucketing/src/lib.rs" +OUTPUT = REPO_ROOT / "evolve_charts/scope_catalog.py" + +ARM = re.compile(r"Self::(\w+) => vec!\[(.*?)\n\s*\],", re.DOTALL) +NAME = re.compile(r'Self::(\w+) => "([^"]*)",') +TOKEN = re.compile(r'^\s*//.*$|get_or_intern_static\(\s*"(?P[^"]+)"\s*\)', re.MULTILINE) + + +def _impl(source: str, category: str) -> str: + start = source.index(f"impl Category for {category}") + return source[start : source.index("fn translated(self)", start)] + + +def _lists(body: str) -> dict[str, list[str]]: + lists = body[: body.index("fn name(self)")] + return { + arm.group(1): [token.group("name") for token in TOKEN.finditer(arm.group(2)) if token.group("name")] + for arm in ARM.finditer(lists) + } + + +def _names(body: str) -> dict[str, str]: + names = body[body.index("fn name(self)") :] + return {variant: name for variant, name in NAME.findall(names) if name} + + +def parse(source: str) -> tuple[dict, dict, list[str]]: + workloads_body = _impl(source, "WorkloadCategory") + workloads: dict[str, list[str]] = {} + for workload, scopes in _lists(workloads_body).items(): + for scope in scopes: + if workload not in workloads.setdefault(scope, []): + workloads[scope].append(workload) + + uncategorized = _lists(_impl(source, "BucketPassCategory")).get("Uncategorized", []) + return workloads, _names(workloads_body), uncategorized + + +def main() -> None: + source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_SOURCE + workloads, workload_names, uncategorized = parse(source_path.read_text(encoding="utf-8")) + + lines = [ + "# Generated by tools/sync_categories.py from", + "# evolve/crates/evolve-frame-bucketing/src/lib.rs. Do not edit by hand.", + "", + "# scope name -> WorkloadCategory variants (Evolve lists a handful of scopes in two)", + "WORKLOADS = {", + *(f" {scope!r}: {names!r}," for scope, names in workloads.items()), + "}", + "", + "# variant -> the name Evolve shows", + "WORKLOAD_NAMES = {", + *(f" {variant!r}: {name!r}," for variant, name in workload_names.items()), + "}", + "", + "# Scopes Evolve's pass bucketing (BucketPassCategory) leaves uncategorized", + "UNCATEGORIZED_PASSES = {", + *(f" {scope!r}," for scope in dict.fromkeys(uncategorized)), + "}", + "", + ] + OUTPUT.write_text("\n".join(lines), encoding="utf-8") + print(f"Wrote {len(workloads)} scope names to {OUTPUT.relative_to(REPO_ROOT)}") + + +if __name__ == "__main__": + main()